Вложенный defaultdict из defaultdict

130

Есть ли способ сделать defaultdict также значением по умолчанию для defaultdict? (т.е. рекурсивный стандарт бесконечного уровня по умолчанию?)

Я хочу уметь:

x = defaultdict(...stuff...)
x[0][1][0]
{}

Итак, я могу x = defaultdict(defaultdict), но это только второй уровень:

x[0]
{}
x[0][0]
KeyError: 0

Есть рецепты, по которым это можно сделать. Но можно ли это сделать, просто используя обычные аргументы defaultdict?

Обратите внимание, что здесь спрашивается, как сделать рекурсивный defaultdict бесконечного уровня, чтобы он отличался от Python: defaultdict of defaultdict? , который заключался в том, как сделать двухуровневое определение по умолчанию.

Я, вероятно, просто воспользуюсь шаблоном связки , но когда я понял, что не знаю, как это сделать, это меня заинтересовало.

Корли Бригман
источник
Возможный дубликат Python: defaultdict или defaultdict?
malioboro
2
Не совсем ... добавил информацию к вопросу, чтобы указать, почему. Хотя это полезный вопрос.
Corley

Ответы:

169

Для произвольного количества уровней:

def rec_dd():
    return defaultdict(rec_dd)

>>> x = rec_dd()
>>> x['a']['b']['c']['d']
defaultdict(<function rec_dd at 0x7f0dcef81500>, {})
>>> print json.dumps(x)
{"a": {"b": {"c": {"d": {}}}}}

Конечно, вы также можете сделать это с помощью лямбды, но я считаю, что лямбды менее читабельны. В любом случае это выглядело бы так:

rec_dd = lambda: defaultdict(rec_dd)
Эндрю Кларк
источник
1
Действительно прекрасный пример, спасибо. Не могли бы вы распространить это на случай, когда данные загружаются из json в defaultdict из defaultdict?
Дэвид Белоград, 04
4
Одна запись. Если вы попытаетесь использовать этот код во время травления, lambdaэто не сработает.
Вячеслав
167

В других ответах здесь рассказывается, как создать объект, defaultdictсодержащий «бесконечно много» defaultdict, но они не могут удовлетворить то, что, как я думаю, могло быть вашей первоначальной потребностью, а именно иметь двухуровневый стандарт по умолчанию.

Возможно, вы искали:

defaultdict(lambda: defaultdict(dict))

Причины, по которым вы можете предпочесть эту конструкцию, следующие:

  • Это более явное решение, чем рекурсивное решение, и, следовательно, более понятное для читателя.
  • Это позволяет "листу" defaultdictбыть чем-то другим, кроме словаря, например: defaultdict(lambda: defaultdict(list))илиdefaultdict(lambda: defaultdict(set))
Крис В.
источник
3
defaultdict (лямбда: defaultdict (список)) Правильная форма?
Юварадж Логанатан
Ой, да, lambdaформа правильная - потому что defaultdict(something)возвращает объект, подобный словарю, но defaultdictожидает вызываемого! Спасибо!
Крис В.
4
Это было отмечено как возможный дубликат другого вопроса ... но это был не мой исходный вопрос. Я знал, как создать двухуровневое определение по умолчанию; чего я не знал, так это как сделать это рекурсивным. Фактически, этот ответ похож на stackoverflow.com/questions/5029934/…
Корли Бригман
Одним из недостатков лямбда-подхода является то, что генерируемые им объекты нельзя мариновать ... но вы можете обойти это, dict(result)
применив преобразование
54

Для этого есть отличный трюк:

tree = lambda: defaultdict(tree)

Затем вы можете создать свой xс помощью x = tree().

BrenBarn
источник
22

Подобно решению BrenBarn, но не содержит treeдважды имени переменной , поэтому работает даже после изменений в словаре переменных:

tree = (lambda f: f(f))(lambda a: (lambda: defaultdict(a(a))))

Затем вы можете создавать каждую новую xс помощью x = tree().


Для этой defверсии мы можем использовать область закрытия функции, чтобы защитить структуру данных от недостатка, когда существующие экземпляры перестают работать, если treeимя повторяется. Выглядит это так:

from collections import defaultdict

def tree():
    def the_tree():
        return defaultdict(the_tree)
    return the_tree()
PTS
источник
4
мне придется подумать об этом (это немного сложнее). но я думаю, ваша точка зрения заключается в том, что если сделать x = tree (), но потом кто-то придет позже и сделает tree = None, этот все равно будет работать, а это не будет?
Corley Brigman
11

Я бы также предложил больше реализации в стиле ООП, которая поддерживает бесконечное вложение, а также правильно отформатирована repr.

class NestedDefaultDict(defaultdict):
    def __init__(self, *args, **kwargs):
        super(NestedDefaultDict, self).__init__(NestedDefaultDict, *args, **kwargs)

    def __repr__(self):
        return repr(dict(self))

Использование:

my_dict = NestedDefaultDict()
my_dict['a']['b'] = 1
my_dict['a']['c']['d'] = 2
my_dict['b']

print(my_dict)  # {'a': {'b': 1, 'c': {'d': 2}}, 'b': {}}
Станислав Цепа
источник
1
Аккуратно! Я добавил сквозную передачу *argsи, **kwargsчто позволяет ему работать как defaultdict, а именно создавать dict с ключевыми аргументами. Это полезно для перехода NestedDefaultDictвjson.load
Киприан Tomoiagă
0

вот рекурсивная функция для преобразования рекурсивного dict по умолчанию в нормальный dict

def defdict_to_dict(defdict, finaldict):
    # pass in an empty dict for finaldict
    for k, v in defdict.items():
        if isinstance(v, defaultdict):
            # new level created and that is the new value
            finaldict[k] = defdict_to_dict(v, {})
        else:
            finaldict[k] = v
    return finaldict

defdict_to_dict(my_rec_default_dict, {})
Доктор XD
источник
0

Я основал это на ответе Эндрю здесь. Если вы хотите загрузить данные из json или существующего dict в nester defaultdict, посмотрите этот пример:

def nested_defaultdict(existing=None, **kwargs):
    if existing is None:
        existing = {}
    if not isinstance(existing, dict):
        return existing
    existing = {key: nested_defaultdict(val) for key, val in existing.items()}
    return defaultdict(nested_defaultdict, existing, **kwargs)

https://gist.github.com/nucklehead/2d29628bb49115f3c30e78c071207775

nucklehead
источник