Python: defaultdict из defaultdict?
Есть ли способ иметь defaultdict(defaultdict(int))
чтобы следующий код работал?
for x in stuff:
d[x.a][x.b] += x.c_int
d
должен быть построен ad-hoc, в зависимости от x.a
а также x.b
элементы.
Я мог бы использовать:
for x in stuff:
d[x.a,x.b] += x.c_int
но тогда я не смог бы использовать:
d.keys()
d[x.a].keys()
7 ответов
Да, вот так:
defaultdict(lambda: defaultdict(int))
Аргумент defaultdict
(в этом случае lambda: defaultdict(int)
) будет вызываться при попытке доступа к ключу, который не существует. Его возвращаемое значение будет установлено как новое значение этого ключа, что в нашем случае означает значение d[Key_doesnt_exist]
будет defaultdict(int)
,
Если вы попытаетесь получить доступ к ключу из этого последнего defaultdict, т.е. d[Key_doesnt_exist][Key_doesnt_exist]
он вернет 0, что является возвращаемым значением аргумента последнего defaultdict, т.е. int()
,
Параметром конструктора defaultdict является функция, которая будет вызываться для создания новых элементов. Так что давайте использовать лямбду!
>>> from collections import defaultdict
>>> d = defaultdict(lambda : defaultdict(int))
>>> print d[0]
defaultdict(<type 'int'>, {})
>>> print d[0]["x"]
0
Начиная с Python 2.7, есть еще лучшее решение, использующее Counter:
>>> from collections import Counter
>>> c = Counter()
>>> c["goodbye"]+=1
>>> c["and thank you"]=42
>>> c["for the fish"]-=5
>>> c
Counter({'and thank you': 42, 'goodbye': 1, 'for the fish': -5})
Некоторые бонусы
>>> c.most_common()[:2]
[('and thank you', 42), ('goodbye', 1)]
Для получения дополнительной информации см. PyMOTW - Коллекции - Типы данных контейнера и Документация Python - коллекции
Предыдущие ответы касались того, как сделать двухуровневый или n-уровневый defaultdict
. В некоторых случаях вам нужен бесконечный:
def ddict():
return defaultdict(ddict)
Применение:
>>> d = ddict()
>>> d[1]['a'][True] = 0.5
>>> d[1]['b'] = 3
>>> import pprint; pprint.pprint(d)
defaultdict(<function ddict at 0x7fcac68bf048>,
{1: defaultdict(<function ddict at 0x7fcac68bf048>,
{'a': defaultdict(<function ddict at 0x7fcac68bf048>,
{True: 0.5}),
'b': 3})})
Я нахожу это немного более элегантным в использовании partial
:
import functools
dd_int = functools.partial(defaultdict, int)
defaultdict(dd_int)
Конечно, это то же самое, что лямбда.
Для справки, можно реализовать общий вложенный defaultdict
заводской метод через:
from collections import defaultdict
from functools import partial
from itertools import repeat
def nested_defaultdict(default_factory, depth=1):
result = partial(defaultdict, default_factory)
for _ in repeat(None, depth - 1):
result = partial(defaultdict, result)
return result()
Глубина определяет количество вложенных словарей перед типом, определенным в default_factory
используется. Например:
my_dict = nested_defaultdict(list, 3)
my_dict['a']['b']['c'].append('e')
Другие правильно ответили на ваш вопрос о том, как заставить работать следующее:
for x in stuff:
d[x.a][x.b] += x.c_int
Альтернативой будет использование кортежей для ключей:
d = defaultdict(int)
for x in stuff:
d[x.a,x.b] += x.c_int
# ^^^^^^^ tuple key
Преимущество этого подхода в том, что он прост и может быть легко расширен. Если вам нужно отображение глубиной в три уровня, просто используйте кортеж из трех предметов для ключа.
defaultdict(lambda: defaultdict(int))
имеет недостаток: он недружелюбен из-за расширения . Хотя вы можете определить функцию по умолчанию глобально, например:
def make_defaultdict_int():
return defaultdict(int)
dd = defaultdict(make_defaultdict_int)
чтобы обойти это, это довольно многословно. К счастью, это довольно легко сделать вpickle
-дружеский способ без этого:
dd = defaultdict(defaultdict(int).copy)
Это делает шаблон пустымdefaultdict(int)
, и проходит границуcopy
метод из него как фабричная функция. Потому чтоdefaultdict
иint
являются поддающимися выборке, как и все связанные методы поддающихся выборке возражений, что делает структуру полностью поддающейся выбору без каких-либо пользовательских определений или дополнительного импорта. В некоторых версиях Python он более эффективен, чем его эквивалент.lambda
(в зависимости от того, где были сосредоточены недавние усилия по оптимизации), но даже если это не так, производительность сопоставима, и это не более многословно, поэтому я предпочитаю этот подход, даже когда травление не вызывает беспокойства, просто потому, что это означает Мне не нужно менять подходы, если/когда маринование станет важным.