Подтвердить что ты не робот

Как изменить поведение 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()

4b9b3361

Ответ 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.