简体   繁体   English

为什么我不能通过继承OrderedDict和defaultdict来创建默认的,有序的dict?

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

My first attempt to combine the features of two dictionaries in the collections module was to create a class that inherits them: 我第一次尝试在collections模块中组合两个字典的功能是创建一个继承它们的类:

from collections import OrderedDict, defaultdict

class DefaultOrderedDict(defaultdict, OrderedDict):
    def __init__(self, default_factory=None, *a, **kw):
        super().__init__(default_factory, *a, **kw)

However, I cannot assign an item to this dictionary: 但是,我无法为此词典分配项目:

d = DefaultOrderedDict(lambda: 0)
d['a'] = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python3.3/collections/__init__.py", line 64, in __setitem__
    self.__map[key] = link = Link()
AttributeError: 'DefaultOrderedDict' object has no attribute '_OrderedDict__map'

Indeed, this question about how to create a similar object has answers that achieve it by extending the OrderedDict class and manually re-implementing the additional methods provided defaultdict . 实际上, 关于如何创建类似对象的这个问题有通过扩展OrderedDict类并手动重新实现defaultdict提供的其他方法来实现它的答案。 Using multiple inheritance would be cleaner. 使用多重继承会更清晰。 Why doesn't it work? 为什么不起作用?

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. 原因是defaultdictinit方法而不是调用MRO中下一个类的__init__调用了PyDict_Type init,因此在OrderedDict的__init__中设置的一些属性如__map从未被初始化,因此错误。

>>> 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__方法:

>>> 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: 所以,当你调用d['a'] = 1时,搜索__setitem__ Python到达OrdereredDict的__setitem__并且他们对未初始化的__map属性的访问引发了错误:


A fix will be to call __init__ on both defaultdict and OrderedDict explicitly: 修复将显式调用defaultdictOrderedDict上的__init__

class DefaultOrderedDict(defaultdict, OrderedDict):
    def __init__(self, default_factory=None, *a, **kw):
        for cls in DefaultOrderedDict.mro()[1:-2]:
            cls.__init__(self, *a, **kw)

Perhaps you are coming from a Java background, but multiple inheritance doesn't do what you'd expect it does in Python. 也许你来自Java背景,但是多重继承并不能达到你在Python中所期望的那样。 Calling super from the init of the defaultOrderedDict calls the super() as the init of defaultdict and never the init of OrderedDict. 从defaultOrderedDict的初始化调用超级调用超()作为defaultdict的init和OrderedDict从未初始化 The map attribute is first defined in the __init function of OrderedDict. map属性首先在 OrderedDict 的__init函数中定义 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. 因此,Bar的构造函数从未被调用过。 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) Pythons方法解析顺序从左到右,直到找到一个具有它所寻找的函数名的类(在本例中为init ),然后忽略右边的所有其他类(在本例中为Bar)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM