Как преобразовать defaultdict defaultdicts [defaultdicts] в dict dicts [of dicts]?

Используя этот ответ, я создал defaultdict defaultdict s. Теперь я хотел бы превратить этот глубоко вложенный объект dict в обычный питон-питон.

from collections import defaultdict

factory = lambda: defaultdict(factory)
defdict = factory()
defdict['one']['two']['three']['four'] = 5

# defaultdict(<function <lambda> at 0x10886f0c8>, {
#             'one': defaultdict(<function <lambda> at 0x10886f0c8>, {
#                 'two': defaultdict(<function <lambda> at 0x10886f0c8>, {
#                     'three': defaultdict(<function <lambda> at 0x10886f0c8>, {
#                         'four': 5})})})})

Я предполагаю, что это неправильное решение:

import json

regdict = json.loads(json.dumps(defdict))

# {u'one': {u'two': {u'three': {u'four': 5}}}}

Кроме того, этот ответ неадекватен, поскольку он не рекурсирует по вложенному dict (s).

Ответ 1

Вы можете переписывать дерево, заменяя каждый экземпляр defaultdict на dict, defaultdict dict:

def default_to_regular(d):
    if isinstance(d, defaultdict):
        d = {k: default_to_regular(v) for k, v in d.items()}
    return d

Демо-версия:

>>> from collections import defaultdict
>>> factory = lambda: defaultdict(factory)
>>> defdict = factory()
>>> defdict['one']['two']['three']['four'] = 5
>>> defdict
defaultdict(<function <lambda> at 0x103098ed8>, {'one': defaultdict(<function <lambda> at 0x103098ed8>, {'two': defaultdict(<function <lambda> at 0x103098ed8>, {'three': defaultdict(<function <lambda> at 0x103098ed8>, {'four': 5})})})})
>>> default_to_regular(defdict)
{'one': {'two': {'three': {'four': 5}}}}

Ответ 2

То, что вы на самом деле пытаетесь сделать, - это defaultdict ваш рекурсивный defaultdict. И вам все равно, верните ли вы dict или defaultdict при рассыпании.

Хотя существует несколько способов решить эту проблему (например, создать подклассу defaultdict с собственным травлением или явно переопределить значение по умолчанию с помощью copyreg), там есть один мертвый тривиальный.

Обратите внимание на ошибку, которую вы получаете при попытке:

>>> pickle.dumps(defdict)
PicklingError: Can't pickle <function <lambda> at 0x10d7f4c80>: attribute lookup <lambda> on __main__ failed

Вы не можете расчехлять функции lambda -defined, потому что они анонимны, что означает, что они никогда не могут быть разбросаны.

Но буквально нет причин, чтобы эта функция определялась lambda. В частности, вы даже не хотите, чтобы это было анонимно, потому что вы явно даете ему имя. Так:

def factory(): return defaultdict(factory)

И вы сделали.

Здесь он находится в действии:

>>> from collections import defaultdict
>>> def factory(): return defaultdict(factory)
>>> defdict = factory()
>>> defdict['one']['two']['three']['four'] = 5
>>> import pickle
>>> pickle.dumps(defdict)
b'\x80\x03ccollections\ndefaultdict\nq\x00c__main__\nfactory\nq\x01\x85q\x02Rq\x03X\x03\x00\x00\x00oneq\x04h\x00h\x01\x85q\x05Rq\x06X\x03\x00\x00\x00twoq\x07h\x00h\x01\x85q\x08Rq\tX\x05\x00\x00\x00threeq\nh\x00h\x01\x85q\x0bRq\x0cX\x04\x00\x00\x00fourq\rK\x05ssss.'

Существуют и другие случаи, когда использование lambda вместо def без уважительной причины вызовет проблемы - вы не можете также инспектировать свои функции во время выполнения, вы ухудшаете трассировку в отладчике и т.д. Используйте lambda когда вы хотите использовать анонимную функцию, или функцию, которую вы можете определить в середине выражения, но не используйте ее для сохранения трех символов ввода.

Ответ 3

Одна из возможностей заключается в том, что вы можете создать свой собственный класс, который можно переключить с режима "defaultdict" в "ванильный" режим. Для больших словарей это было бы намного, намного быстрее. Речь идет о переопределении метода getitem и наличии соответствующей переменной экземпляра для управления поведением.