How to change behavior of dict() for an instance

前端 未结 6 1266
悲哀的现实
悲哀的现实 2021-01-30 09:21

So I\'m writing a class that extends a dictionary which right now uses a method \"dictify\" to transform itself into a dict. What I would like to do instead though is change it

相关标签:
6条回答
  • 2021-01-30 10:06

    You can't do it.

    I deleted my previous answer, because I found after looking at the source code, that if you call dict(d) on a d that is a subclass of dict, it makes a fast copy of the underlying hash in C, and returns a new dict object.

    Sorry.

    If you really want this behavior, you'll need to create a RecursiveDict class that doesn't inherit from dict, and implement the __iter__ interface.

    0 讨论(0)
  • 2021-01-30 10:11

    edit: As ironchefpython pointed out in comments, this isn't actually doing what I thought it did, as in my example b[1] is still a RecursiveDict. This may still be useful, as you essentially get an object pretty similar Rob Cowie's answer, but it is built on defaultdict.


    You can get the behavior you want (or something very similar) by overriding __repr__, check this out:

    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'>
    

    There may be a better way to get to a normal dictionary view of the defaultdict than dict(self) but I couldn't find one, comment if you know how.

    0 讨论(0)
  • 2021-01-30 10:12

    Do you want just to print it like a dict ? use this:

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

    Maybe you are looking for __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]
    
    0 讨论(0)
  • 2021-01-30 10:14

    You need to override __iter__.

    def __iter__(self): 
        return iter((k, (v.dictify() if isinstance(v, dict) else v)) 
                    for (k, v) in self.items())
    

    Instead of self.items(), you should use self.iteritems() on Python 2.

    Edit: OK, This seems to be your problem:

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

    So this method doesn't work if the object you're calling dict() on is a subclass of dict.

    Edit 2: To be clear, defaultdict is a subclass of dict. dict(a_defaultdict) is still a no-op.

    0 讨论(0)
  • 2021-01-30 10:19

    Once you have your dictify function working just do

    dict = dictify
    

    Update: Here is a short way to have this recursive dict:

    >>> def RecursiveDict():
    ...   return defaultdict(RecursiveDict)
    

    Then you can:

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

    I don't see a neat way to implement dictify.

    0 讨论(0)
  • 2021-01-30 10:21

    Nothing wrong with your approach, but this is similar to the Autovivification feature of Perl, which has been implemented in Python in this question. Props to @nosklo for this.

    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

    As suggested by @Rosh Oxymoron, using __missing__ results in a more concise implementation. Requires Python >= 2.5

    class RecursiveDict(dict):
        """Implementation of perl's autovivification feature."""
        def __missing__(self, key):
            value = self[key] = type(self)()
            return value
    
    0 讨论(0)
提交回复
热议问题