Как изменить поведение dict() для экземпляра
Итак, я пишу класс, который расширяет словарь, который прямо использует метод "диктует", чтобы преобразовать себя в dict. Вместо этого я хотел бы изменить это, так что вызов dict() объекта приводит к одному и тому же поведению, но я не знаю, какой метод переопределить. Разве это невозможно, или я пропущу что-то совершенно очевидное? (И да, я знаю, что код ниже не работает, но я надеюсь, что это иллюстрирует то, что я пытаюсь сделать.)
from collections import defaultdict
class RecursiveDict(defaultdict):
'''
A recursive default dict.
>>> a = RecursiveDict()
>>> a[1][2][3] = 4
>>> a.dictify()
{1: {2: {3: 4}}}
'''
def __init__(self):
super(RecursiveDict, self).__init__(RecursiveDict)
def dictify(self):
'''Get a standard dictionary of the items in the tree.'''
return dict([(k, (v.dictify() if isinstance(v, dict) else v))
for (k, v) in self.items()])
def __dict__(self):
'''Get a standard dictionary of the items in the tree.'''
print [(k, v) for (k, v) in self.items()]
return dict([(k, (dict(v) if isinstance(v, dict) else v))
for (k, v) in self.items()])
РЕДАКТ. Чтобы показать проблему более четко:
>>> b = RecursiveDict()
>>> b[1][2][3] = 4
>>> b
defaultdict(<class '__main__.RecursiveDict'>, {1: defaultdict(<class '__main__.RecursiveDict'>, {2: defaultdict(<class '__main__.RecursiveDict'>, {3: 4})})})
>>> dict(b)
{1: defaultdict(<class '__main__.RecursiveDict'>, {2: defaultdict(<class '__main__.RecursiveDict'>, {3: 4})})}
>>> b.dictify()
{1: {2: {3: 4}}}
Я хочу, чтобы dict (b) был таким же, как b.dictify()
Ответы
Ответ 1
В вашем подходе нет ничего плохого, но это похоже на функцию Autovivification Perl, которая была реализована в Python в этом вопросе. Подкрепление к @nosklo для этого.
class RecursiveDict(dict):
"""Implementation of perl autovivification feature."""
def __getitem__(self, item):
try:
return dict.__getitem__(self, item)
except KeyError:
value = self[item] = type(self)()
return value
>>> a = RecursiveDict()
>>> a[1][2][3] = 4
>>> dict(a)
{1: {2: {3: 4}}}
ИЗМЕНИТЬ
Как было предложено @Rosh Oxymoron, использование __missing__
приводит к более сжатой реализации. Требуется Python >= 2.5
class RecursiveDict(dict):
"""Implementation of perl autovivification feature."""
def __missing__(self, key):
value = self[key] = type(self)()
return value
Ответ 2
edit. Как отметил в комментариях ironchefpyththon, на самом деле это не делает то, что я думал, так как в моем примере b[1]
все еще есть RecursiveDict
. Это может быть полезно, поскольку вы, по сути, получаете объект, похожий на ответ Роба Коуи, но он построен на defaultdict
.
Вы можете получить нужное поведение (или что-то очень похожее), переопределив __repr__
, проверьте это:
class RecursiveDict(defaultdict):
def __init__(self):
super(RecursiveDict, self).__init__(RecursiveDict)
def __repr__(self):
return repr(dict(self))
>>> a = RecursiveDict()
>>> a[1][2][3] = 4
>>> a # a looks like a normal dict since repr is overridden
{1: {2: {3: 4}}}
>>> type(a)
<class '__main__.RecursiveDict'>
>>> b = dict(a)
>>> b # dict(a) gives us a normal dictionary
{1: {2: {3: 4}}}
>>> b[5][6] = 7 # obviously this won't work anymore
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 5
>>> type(b)
<type 'dict'>
Может быть лучший способ получить нормальный словарь-словарь defaultdict
, чем dict(self)
, но я не смог его найти, комментарий, если вы знаете, как это сделать.
Ответ 3
Вы хотите просто распечатать его, как dict? используйте это:
from collections import defaultdict
class RecursiveDict(defaultdict):
'''
A recursive default dict.
>>> a = RecursiveDict()
>>> a[1][2][3] = 4
>>> a.dictify()
{1: {2: {3: 4}}}
>>> dict(a)
{1: {2: {3: 4}}}
'''
def __init__(self):
super(RecursiveDict, self).__init__(RecursiveDict)
def dictify(self):
'''Get a standard dictionary of the items in the tree.'''
return dict([(k, (v.dictify() if isinstance(v, dict) else v))
for (k, v) in self.items()])
def __dict__(self):
'''Get a standard dictionary of the items in the tree.'''
print [(k, v) for (k, v) in self.items()]
return dict([(k, (dict(v) if isinstance(v, dict) else v))
for (k, v) in self.items()])
def __repr__(self):
return repr(self.dictify())
Возможно, вы ищете __missing__
:
class RecursiveDict(dict):
'''
A recursive default dict.
>>> a = RecursiveDict()
>>> a[1][2][3] = 4
>>> a
{1: {2: {3: 4}}}
>>> dict(a)
{1: {2: {3: 4}}}
'''
def __missing__(self, key):
self[key] = self.__class__()
return self[key]
Ответ 4
Вы не можете этого сделать.
Я удалил свой предыдущий ответ, потому что после поиска исходного кода я обнаружил, что если вы вызываете dict(d)
в d
, который является подклассом dict
, он делает быструю копию базового хэша в C и возвращает новый объект dict.
К сожалению.
Если вы действительно хотите этого поведения, вам нужно создать класс RecursiveDict
, который не наследуется от dict
, и реализовать интерфейс __iter__
.
Ответ 5
Вам нужно переопределить __iter__
.
def __iter__(self):
return iter((k, (v.dictify() if isinstance(v, dict) else v))
for (k, v) in self.items())
Вместо self.items()
вы должны использовать self.iteritems()
на Python 2.
Изменить: ОК. Кажется, это ваша проблема:
>>> class B(dict): __iter__ = lambda self: iter(((1, 2), (3, 4)))
...
>>> b = B()
>>> dict(b)
{}
>>> class B(list): __iter__ = lambda self: iter(((1, 2), (3, 4)))
...
>>> b = B()
>>> dict(b)
{1: 2, 3: 4}
Таким образом, этот метод не работает, если объект, который вы вызываете dict()
on, является подклассом dict.
Изменить 2: чтобы быть ясным, defaultdict
является подклассом dict
. dict (a_defaultdict) по-прежнему не работает.
Ответ 6
Как только ваша функция dictify работает, просто
dict = dictify
Обновление:
Вот короткий способ иметь этот рекурсивный dict:
>>> def RecursiveDict():
... return defaultdict(RecursiveDict)
Затем вы можете:
d[1][2][3] = 5
d[1][2][4] = 6
>>> d
defaultdict(<function ReturnsRecursiveDict at 0x7f3ba453a5f0>, {1: defaultdict(<function ReturnsRecursiveDict at 0x7f3ba453a5f0>, {2: defaultdict(<function ReturnsRecursiveDict at 0x7f3ba453a5f0>, {3: 5, 4: 6})})})
Я не вижу аккуратного способа реализации dictify.