Использовать случаи для метода 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}