Варианты использования метода 'setdefault' dict

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

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

16 ответов


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

вероятно, наиболее распространенный вариант использования: группировка элементов (в несортированных данных, иначе используйте 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-ish со многими заголовками - некоторые из них необязательны, но вы хотите по умолчанию для них:

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 )

Я обычно использую 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)

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


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

например, мне нужен словарь для сопоставления строк с уникальными ints. 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 крошечный класс I строит, который автоматически увеличивает int и возвращает его значение:

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

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

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


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


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

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

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


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

однако из стандартной библиотеки (Python 2.6, _threadinglocal.py):

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

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

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

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


вот несколько примеров 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'])

один недостаток defaultdict над dict (dict.setdefault), что


Я часто использую setdefault, когда, получите это, установив значение по умолчанию (!!!) в словаре; несколько часто ОС.environ dictionary:

# Set the venv dir if it isn't already overridden:
os.environ.setdefault('VENV_DIR', '/my/default/path')

менее кратко, это выглядит так:

# Set the venv dir if it isn't already overridden:
if 'VENV_DIR' not in os.environ:
    os.environ['VENV_DIR'] = '/my/default/path')

стоит отметить, что вы также можете использовать выходной переменной:

venv_dir = os.environ.setdefault('VENV_DIR', '/my/default/path')

но это менее необходимо, чем это было до defaultdicts существовало.


другой вариант использования, который я не думаю, что было сказано выше. Иногда вы сохраняете Cache dict объектов по их идентификатору, где первичный экземпляр находится в кэше, и вы хотите установить кэш при отсутствии.

return self.objects_by_id.setdefault(obj.id, obj)

это полезно, когда вы всегда хотите сохранить один экземпляр на отдельный id независимо от того, как вы получаете obj каждый раз. Например, когда атрибуты объекта обновляются в памяти и сохранение в хранилище откладывается.


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

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

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() как последний шаг сохранения вычисляемый составной элемент-если другой уже сохранен, то он используется вместо нового, гарантируя уникальные члены перечисления.


как большинство ответов государственной 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 всегда будет вызывать функцию рекурсивно.


[Edit] очень неправильно! setdefault всегда будет вызывать long_computation, Python стремится.

расширение ответа Таттла. Для меня лучшим вариантом использования является механизм кэша. Вместо:

if x not in memo:
   memo[x]=long_computation(x)
return memo[x]

который потребляет 3 строки и 2 или 3 поиска,Я бы с удовольствием написал :

return memo.setdefault(x, long_computation(x))

Мне нравится ответ дан здесь:

http://stupidpythonideas.blogspot.com/2013/08/defaultdict-vs-setdefault.html

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


другой вариант использования для setdefault() и когда вы не хотите перезаписать значение уже установленного ключа. defaultdict перезаписывает, в то время как setdefault() нет. Для вложенных словарей чаще бывает так, что вы хотите установить значение по умолчанию, только если ключ еще не установлен, потому что вы не хотите удалять настоящий вложенный словарь. Это когда вы используете setdefault().

пример defaultdict:

>>> from collection import defaultdict()
>>> foo = defaultdict()
>>> foo['a'] = 4
>>> foo['a'] = 2
>>> print(foo)
defaultdict(None, {'a': 2})

setdefault не перезаписать:

>>> bar = dict()
>>> bar.setdefault('a', 4)
>>> bar.setdefault('a', 2)
>>> print(bar)
{'a': 4}

я переписал принятый ответ и облегчил его для новичков.

#break it down and understand it intuitively.
new = {}
for (key, value) in data:
    if key not in new:
        new[key] = [] # this is core of setdefault equals to new.setdefault(key, [])
        new[key].append(value)
    else:
        new[key].append(value)


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



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

кроме того, я классифицировал методы как ссылку:

dict_methods_11 = {
            'views':['keys', 'values', 'items'],
            'add':['update','setdefault'],
            'remove':['pop', 'popitem','clear'],
            'retrieve':['get',],
            'copy':['copy','fromkeys'],}