Использовать случаи для метода setdefault dict

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

  • Что такое setdefault, по-прежнему полезное, сегодня в Python 2.6/2.7?
  • Какие популярные варианты использования setdefault были заменены на collections.defaultdict?

Ответ 1

Можно сказать, что 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-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 )

Ответ 2

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

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)

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

Ответ 3

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

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

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

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, мне бы очень хотелось его увидеть.

Ответ 4

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

Ответ 5

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

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

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

Ответ 6

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

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

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

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

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

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

Ответ 7

Вот несколько примеров 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 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 still true that ('Toyota' in e['Cars'])

Ответ 8

Вероятный недостаток defaultdict over dict (dict.setdefault) заключается в том, что объект defaultdict создает новый элемент каждый раз, но не существующий ключ (например, с print, ==). Кроме того, класс defaultdict менее редок, чем класс dict (сериализация, представление и т.д.).

P.S. Функции (методы) IMO, не предназначенные для мутации объекта, не должны мутировать объект.

Ответ 9

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

# 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.

Ответ 10

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

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

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

Ответ 11

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

Например, (Int)Flag Enum in 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.

Ответ 12

[Edit] Неправильно!. setdefault всегда вызывал long_comput, Python быстр.

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

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

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

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

Ответ 13

Если требуемое значение по умолчанию не всегда одинаковое или оно требуется только для определенных клавиш, но оно предпочитает не использовать его для других, можно подумать об использовании setdefault:

d = {}
...
# `i` should default to zero
i = d.setdefault(key, 0)
...
# `s` should default to an empty string
s = d.setdefault(key, '')
...

 

d = {}
...
# v should always default to a list
v = d.setdefault(key, [])
...
try:
    # EAFP, but I need the dict to raise a KeyError if the key is not found.
    w = d[k2]
except KeyError:
    ...
...

Ответ 14

Мне нравится ответ, приведенный здесь:

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

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

Ответ 15

Другой вариант использования 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}