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

12

我第一次尝试在collections模块中将两个字典的特性结合起来,是创建一个继承它们的类:

from collections import OrderedDict, defaultdict

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

然而,我无法将项分配给此字典:

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'

实际上,这个关于如何创建类似对象的问题有一些答案通过扩展 OrderedDict 类并手动重新实现提供的 defaultdict 的其他方法来实现它。使用多重继承会更清晰,但为什么不行呢?


еҸҰеӨ–пјҢдҪ еҸҜд»ҘдҪҝз”Ёdefaultdict(int)д»Јжӣҝdefaultdict(lambda: 0)гҖӮ - 1''
2个回答

5
原因是defaultdictinit方法不调用MRO中下一个类的__init__,而是调用PyDict_Type的init方法,因此一些属性(如OrderedDict的__init__中设置的__map)从未初始化,导致错误发生。
>>> DefaultOrderedDict.mro()
[<class '__main__.DefaultOrderedDict'>,
 <class 'collections.defaultdict'>,
 <class 'collections.OrderedDict'>,
 <class 'dict'>, <class 'object'>]

defaultdict没有自己的__setitem__方法:

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

因此,当您调用d['a'] = 1时,Python会找到OrdereredDict的__setitem__函数,并且在那里访问未初始化的__map属性引发了错误:

解决方法是显式地在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)

1
为什么不能交换基类的顺序:class OrderedDefaultDict(OrderedDict, defaultdict):,然后在你的 __init__(self, default_factory=None, *args, **kwargs) 中包含两行代码:super(OrderedDefaultDict, self).__init__(*args, **kwargs)self.default_dict = default_dict - Sam
1
我刚刚尝试了Sam的建议,它起作用了。(好吧,在我将default_dict更改为default_factory之后!) - samwyse

4

也许您来自Java背景,但在Python中,多重继承并不像您期望的那样工作。在defaultOrderedDict的init中调用super()会将其作为defaultdict的init而不是OrderedDict的init进行调用。map属性首先在OrderedDict的__init__函数中定义。以下是实现方式(来自源代码):

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)

请注意,这与属性是私有的无关。一个带有多重继承的最小示例可以说明这一点:
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'

所以,Bar的构造函数从未被调用。Python的方法解析顺序是从左到右,直到找到具有所需函数名(在这种情况下为init)的类,然后忽略右侧的所有其他类(在这种情况下为Bar)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接