Why can't I create a default, ordered dict by inheriting OrderedDict and defaultdict?

后端 未结 2 1379
日久生厌
日久生厌 2021-02-19 19:05

My first attempt to combine the features of two dictionaries in the collections module was to create a class that inherits them:

from collections im         


        
相关标签:
2条回答
  • 2021-02-19 19:52

    The reason is that the init method of defaultdict instead of calling __init__ of the next class in MRO calls init of PyDict_Type hence some of the attributes like __map that are set in OrderedDict's __init__ are never initialized, hence the error.

    >>> DefaultOrderedDict.mro()
    [<class '__main__.DefaultOrderedDict'>,
     <class 'collections.defaultdict'>,
     <class 'collections.OrderedDict'>,
     <class 'dict'>, <class 'object'>]
    

    And defaultdict don't have their own __setitem__ method:

    >>> defaultdict.__setitem__
    <slot wrapper '__setitem__' of 'dict' objects>
    >>> dict.__setitem__
    <slot wrapper '__setitem__' of 'dict' objects>
    >>> OrderedDict.__setitem__
    <unbound method OrderedDict.__setitem__>
    

    So, when you called d['a'] = 1, in search of __setitem__ Python reached OrdereredDict's __setitem__ and their the access of uninitialized __map attribute raised the error:


    A fix will be to call __init__ on both defaultdict and OrderedDict explicitly:

    class DefaultOrderedDict(defaultdict, OrderedDict):
        def __init__(self, default_factory=None, *a, **kw):
            for cls in DefaultOrderedDict.mro()[1:-2]:
                cls.__init__(self, *a, **kw)
    
    0 讨论(0)
  • 2021-02-19 19:55

    Perhaps you are coming from a Java background, but multiple inheritance doesn't do what you'd expect it does in Python. Calling super from the init of the defaultOrderedDict calls the super() as the init of defaultdict and never the init of OrderedDict. The map attribute is first defined in the __init function of OrderedDict. The implementation is the following (from source):

    def __init__(self, *args, **kwds):
        '''Initialize an ordered dictionary.  The signature is the same as
        regular dictionaries, but keyword arguments are not recommended because
        their insertion order is arbitrary.
    
        '''
        if len(args) > 1:
            raise TypeError('expected at most 1 arguments, got %d' % len(args))
        try:
            self.__root
        except AttributeError:
            self.__root = root = []                     # sentinel node
            root[:] = [root, root, None]
            self.__map = {}
        self.__update(*args, **kwds)
    

    Note that this doesn't have to do with the attribute being private. A minimal example with multiple inheritance can illustrate this:

    class Foo:
        def __init__(self):
            self.foo=2
    
    class Bar:
        def __init__(self):
            self.bar=1
    
    class FooBar(Foo,Bar):
         def __init__(self):
            super().__init__()
    
    fb = FooBar()
    
    fb.foo
    >>2 
    fb.bar
    >>AttributeError: 'FooBar' object has no attribute 'bar'
    

    So, the constructor of Bar was never called. Pythons method resolution order goes from left to right until it finds a class with the function name it seeks (in this case init) and then ignores all other classes on the right (in this case Bar)

    0 讨论(0)
提交回复
热议问题