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(в зависимости от того, где были сосредоточены недавние усилия по оптимизации), но даже если это не так, производительность сопоставима, и это не более многословно, поэтому я предпочитаю этот подход, даже когда травление не вызывает беспокойства, просто потому, что это означает Мне не нужно менять подходы, если/когда маринование станет важным.

Другие вопросы по тегам