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()
Ответы
Ответ 1
Да вот так:
defaultdict(lambda: defaultdict(int))
Аргумент a defaultdict
(в данном случае lambda: defaultdict(int)
) будет вызываться при попытке получить доступ к ключу, который не существует. Возвращаемое значение будет установлено как новое значение этого ключа, что означает, что в нашем случае значение d[Key_doesnt_exist]
будет defaultdict(int)
.
Если вы попытаетесь получить доступ к ключу из этого последнего defaultdict, то есть d[Key_doesnt_exist][Key_doesnt_exist]
, он вернет 0, что является возвращаемым значением аргумента последнего значения defaultdict, т.е. int()
.
Ответ 2
Параметр конструктора defaultdict - это функция, которая будет вызываться для создания новых элементов. Поэтому позвольте использовать лямбда!
>>> from collections import defaultdict
>>> d = defaultdict(lambda : defaultdict(int))
>>> print d[0]
defaultdict(<type 'int'>, {})
>>> print d[0]["x"]
0
С Python 2.7 существует еще лучшее решение с использованием счетчика:
>>> 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 Документация - коллекции
Ответ 3
Я считаю несколько более элегантным использовать partial
:
import functools
dd_int = functools.partial(defaultdict, int)
defaultdict(dd_int)
Конечно, это то же самое, что и лямбда.
Ответ 4
Другие ответили правильно на ваш вопрос о том, как заставить следующее работать:
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
Самое приятное в этом подходе заключается в том, что оно простое и может быть легко расширено. Если вам нужно отображать три уровня в глубину, просто используйте три элемента для ключа.
Ответ 5
Для справки, можно реализовать общий вложенный defaultdict
фабрики 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()