Варианты использования для метода dict 'setdefault' - PullRequest
175 голосов
/ 14 августа 2010

Добавление collections.defaultdict в Python 2.5 значительно уменьшило необходимость в методе dict setdefault. Этот вопрос для нашего коллективного образования:

  1. Для чего все еще полезно setdefault, сегодня в Python 2.6 / 2.7?
  2. Какие популярные варианты использования setdefault были заменены на collections.defaultdict?

Ответы [ 16 ]

185 голосов
/ 14 августа 2010

Можно сказать, defaultdict полезно для настроек по умолчанию перед заполнением dict и setdefault полезно для установки значений по умолчанию во время или после заполнения dict .

Вероятно, наиболее распространенный вариант использования: группировка элементов (в несортированных данных, в противном случае используется itertools.groupby)

# really verbose
new = {}
for (key, value) in data:
    if key in new:
        new[key].append( value )
    else:
        new[key] = [value]


# easy with setdefault
new = {}
for (key, value) in data:
    group = new.setdefault(key, []) # key might exist already
    group.append( value )


# even simpler with defaultdict 
new = defaultdict(list)
for (key, value) in data:
    new[key].append( value ) # all keys have a default already

Иногда вы хотите убедиться, что определенные ключи существуют после создания dict. defaultdict не работает в этом случае, потому что он создает ключи только при явном доступе. Представьте, что вы используете HTTP-иш со многими заголовками - некоторые являются необязательными, но вы хотите использовать для них значения по умолчанию:

headers = parse_headers( msg ) # parse the message, get a dict
# now add all the optional headers
for headername, defaultvalue in optional_headers:
    headers.setdefault( headername, defaultvalue )
28 голосов
/ 14 августа 2010

Я обычно использую setdefault для указания ключевых слов, например, в этой функции:

def notify(self, level, *pargs, **kwargs):
    kwargs.setdefault("persist", level >= DANGER)
    self.__defcon.set(level, **kwargs)
    try:
        kwargs.setdefault("name", self.client.player_entity().name)
    except pytibia.PlayerEntityNotFound:
        pass
    return _notify(level, *pargs, **kwargs)

Отлично подходит для настройки аргументов в оболочках вокруг функций, которые принимают аргументы ключевых слов.

15 голосов
/ 30 мая 2011

defaultdict отлично подходит, когда значение по умолчанию статично, как новый список, но не так сильно, если оно динамическое.

Например, мне нужен словарь для сопоставления строк с уникальными целочисленными значениями. defaultdict(int) всегда будет использовать 0 в качестве значения по умолчанию. Аналогично, defaultdict(intGen()) всегда производит 1.

Вместо этого я использовал обычный дикт:

nextID = intGen()
myDict = {}
for lots of complicated stuff:
    #stuff that generates unpredictable, possibly already seen str
    strID = myDict.setdefault(myStr, nextID())

Обратите внимание, что dict.get(key, nextID()) недостаточно, потому что мне нужно иметь возможность ссылаться и на эти значения позже.

intGen - это крошечный класс, который я создаю, который автоматически увеличивает int и возвращает его значение:

class intGen:
    def __init__(self):
        self.i = 0

    def __call__(self):
        self.i += 1
    return self.i

Если у кого-то есть способ сделать это с defaultdict Я бы с удовольствием посмотрел.

9 голосов
/ 22 января 2014

Я использую setdefault(), когда хочу значение по умолчанию в OrderedDict. Не существует стандартной коллекции Python, которая бы выполняла и то, и другое, но есть способов для реализации такой коллекции.

7 голосов
/ 09 июня 2011

Как сказал Мухаммед, есть ситуации, в которых вы только иногда хотите установить значение по умолчанию.Прекрасным примером этого является структура данных, которая сначала заполняется, а затем запрашивается.

Рассмотрим три.При добавлении слова, если подузел необходим, но отсутствует, он должен быть создан для расширения дерева.При запросе наличия слова отсутствующий подузел указывает, что слово отсутствует и его не следует создавать.

Дефолт по умолчанию не может этого сделать.Вместо этого следует использовать обычный dict с методами get и setdefault.

5 голосов
/ 14 августа 2010

Теоретически, setdefault все равно будет удобно, если вы иногда хотите установить значение по умолчанию, а иногда нет. В реальной жизни я не встречал такого случая использования.

Однако из стандартной библиотеки (Python 2.6, _threadinglocal.py) возникает интересный пример использования:

>>> mydata = local()
>>> mydata.__dict__
{'number': 42}
>>> mydata.__dict__.setdefault('widgets', [])
[]
>>> mydata.widgets
[]

Я бы сказал, что использование __dict__.setdefault - довольно полезный случай.

Редактировать : Как оказалось, это единственный пример в стандартной библиотеке, и он есть в комментарии. Так что, может быть, одного случая недостаточно, чтобы оправдать существование setdefault. Тем не менее, вот объяснение:

Объекты хранят свои атрибуты в атрибуте __dict__. Как это бывает, атрибут __dict__ доступен для записи в любое время после создания объекта. Это также словарь, а не defaultdict. Для объектов в общем случае не имеет смысла иметь __dict__ как defaultdict, потому что это сделало бы каждый объект, имеющий все допустимые идентификаторы в качестве атрибутов. Поэтому я не могу предвидеть каких-либо изменений в объектах Python, избавляющихся от __dict__.setdefault, за исключением полного удаления, если это считается бесполезным.

3 голосов
/ 05 июля 2018

Поскольку большинство ответов указывают, что setdefault или defaultdict позволят вам установить значение по умолчанию, когда ключ не существует. Тем не менее, я хотел бы указать на небольшое предостережение в отношении случаев использования setdefault. Когда интерпретатор Python выполняет setdefault, он всегда оценивает второй аргумент функции, даже если ключ существует в словаре. Например:

In: d = {1:5, 2:6}

In: d
Out: {1: 5, 2: 6}

In: d.setdefault(2, 0)
Out: 6

In: d.setdefault(2, print('test'))
test
Out: 6

Как видите, print также было выполнено, хотя в словаре уже есть 2. Это становится особенно важным, если вы планируете использовать setdefault, например, для оптимизации типа memoization. Если вы добавите рекурсивный вызов функции в качестве второго аргумента в setdefault, вы не получите от этого никакой производительности, поскольку Python всегда будет вызывать функцию рекурсивно.

3 голосов
/ 03 декабря 2016

Один недостаток defaultdict над dict (dict.setdefault) состоит в том, что объект defaultdict создает новый элемент EVERYTIME дается несуществующий ключ (например, с ==, * 1008)*).Кроме того, класс defaultdict, как правило, менее распространен, чем класс dict, его сериализовать IME труднее.

PS IMO-функции | методы, не предназначенные для мутации объекта, не должны мутировать объект.

2 голосов
/ 22 сентября 2014

Вот несколько примеров setdefault, чтобы показать его полезность:

"""
d = {}
# To add a key->value pair, do the following:
d.setdefault(key, []).append(value)

# To retrieve a list of the values for a key
list_of_values = d[key]

# To remove a key->value pair is still easy, if
# you don't mind leaving empty lists behind when
# the last value for a given key is removed:
d[key].remove(value)

# Despite the empty lists, it's still possible to 
# test for the existance of values easily:
if d.has_key(key) and d[key]:
    pass # d has some values for key

# Note: Each value can exist multiple times!
"""
e = {}
print e
e.setdefault('Cars', []).append('Toyota')
print e
e.setdefault('Motorcycles', []).append('Yamaha')
print e
e.setdefault('Airplanes', []).append('Boeing')
print e
e.setdefault('Cars', []).append('Honda')
print e
e.setdefault('Cars', []).append('BMW')
print e
e.setdefault('Cars', []).append('Toyota')
print e

# NOTE: now e['Cars'] == ['Toyota', 'Honda', 'BMW', 'Toyota']
e['Cars'].remove('Toyota')
print e
# NOTE: it's still true that ('Toyota' in e['Cars'])
1 голос
/ 23 января 2017

Один очень важный пример использования, с которым я только что наткнулся: dict.setdefault() отлично подходит для многопоточного кода, когда требуется только один канонический объект (в отличие от нескольких объектов, которые оказываются равными).

Например, перечисление (Int)Flag в Python 3.6.0 содержит ошибку : если несколько потоков конкурируют за составной (Int)Flag член, может оказаться более одного:

from enum import IntFlag, auto
import threading

class TestFlag(IntFlag):
    one = auto()
    two = auto()
    three = auto()
    four = auto()
    five = auto()
    six = auto()
    seven = auto()
    eight = auto()

    def __eq__(self, other):
        return self is other

    def __hash__(self):
        return hash(self.value)

seen = set()

class cycle_enum(threading.Thread):
    def run(self):
        for i in range(256):
            seen.add(TestFlag(i))

threads = []
for i in range(8):
    threads.append(cycle_enum())

for t in threads:
    t.start()

for t in threads:
    t.join()

len(seen)
# 272  (should be 256)

Решение состоит в том, чтобы использовать setdefault() в качестве последнего шага сохранения вычисленного составного элемента - если другой уже был сохранен, то он используется вместо нового, гарантируя уникальные члены Enum.

...