Как изменить поведение dict () для экземпляра - PullRequest
18 голосов
/ 21 июля 2011

Итак, я пишу класс, который расширяет словарь, который сейчас использует метод "dictify", чтобы превратить себя в 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 ()

Ответы [ 6 ]

28 голосов
/ 21 июля 2011

Ничего плохого в вашем подходе, но это похоже на функцию Autovivification в Perl, которая была реализована в Python в этом вопросе . Реквизит @nosklo для этого.

class RecursiveDict(dict):
    """Implementation of perl's 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}}}

EDIT

Как подсказывает @Rosh Oxymoron, использование __missing__ приводит к более краткой реализации. Требуется Python> = 2.5

class RecursiveDict(dict):
    """Implementation of perl's autovivification feature."""
    def __missing__(self, key):
        value = self[key] = type(self)()
        return value
2 голосов
/ 22 июля 2011

Вы не можете сделать это.

Я удалил свой предыдущий ответ, потому что после просмотра исходного кода обнаружил, что если вы вызываете dict(d) на d, который является подклассом dict, он делает быструю копиюлежащий в основе хеш в C, и возвращает новый объект dict.

Извините.

Если вы действительно хотите такое поведение, вам нужно создать класс RecursiveDict, который не наследуется отdict и реализовать интерфейс __iter__.

2 голосов
/ 22 июля 2011

Вы хотите просто напечатать это как дикт?используйте это:

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]
2 голосов
/ 22 июля 2011

edit : Как уже упоминалось в комментариях ironchefpython, на самом деле это не то, что я думал, поскольку в моем примере 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), но я не смог его найти, прокомментируйте, если знаете как.

1 голос
/ 21 июля 2011

Вам нужно переопределить __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(), является подклассом dict.

Редактировать 2: Для ясности, defaultdict является подклассом dict.dict (a_defaultdict) по-прежнему не используется.

0 голосов
/ 21 июля 2011

После того, как ваша функция диктофона заработала, просто выполните

dict = dictify

Обновление: Вот короткий способ получить этот рекурсивный диктант:

>>> 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})})})

Я не вижу изящного способа реализации диктофона.

...