Варианты использования метода 'setdefault' dict
дополнение collections.defaultdict
в Python 2.5 значительно уменьшена потребность в dict
' s setdefault
метод. Этот вопрос для нашего коллективного образования:
- что это
setdefault
еще полезно, сегодня в Python 2.6/2.7? - какие популярные случаи использования
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
Я хотел бы увидеть это.
как Мухаммад сказал, Есть ситуации, в которых вы только иногда хотите установить значение по умолчанию. Отличным примером этого является структура данных, которая сначала заполняется, а затем запрашивается.
рассмотрим 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'])
Я часто использую 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'],}