【问题标题】:Why can't I create a default, ordered dict by inheriting OrderedDict and defaultdict?为什么我不能通过继承 OrderedDict 和 defaultdict 创建一个默认的有序 dict?
【发布时间】:2015-02-27 00:26:00
【问题描述】:

我第一次尝试在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'

确实,this question about how to create a similar object 的答案可以通过扩展OrderedDict 类并手动重新实现defaultdict 提供的其他方法来实现。使用多重继承会更干净。为什么它不起作用?

【问题讨论】:

  • 顺便说一句,您可以使用defaultdict(int) 而不是defaultdict(lambda: 0)

标签: python python-3.x multiple-inheritance defaultdict ordereddictionary


【解决方案1】:

原因是 init method of defaultdict 而不是调用 MRO 中下一个类的 __init__ 调用 PyDict_Type 的 init 因此在 OrderedDict's __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 时,在搜索__setitem__ 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)

【讨论】:

  • 你为什么不能交换基类的顺序:class OrderedDefaultDict(OrderedDict, defaultdict):,然后让你的__init__(self, default_factory=None, *args, **kwargs)包含两行:super(OrderedDefaultDict, self).__init__(*args, **kwargs)self.default_dict = default_dict
  • 我刚刚尝试了 Sam 的建议,它奏效了。 (好吧,在我将 default_dict 更改为 default_factory 之后!)
【解决方案2】:

也许您来自 Java 背景,但多重继承并没有像您期望的那样在 Python 中实现。从 defaultOrderedDict 的 init 调用 super 调用 super() 作为 defaultdict 的 init,而不是 OrderedDict 的 initma​​p 属性首先在 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)

【讨论】:

    猜你喜欢
    • 2020-08-17
    • 1970-01-01
    • 2010-09-23
    • 2020-04-28
    • 1970-01-01
    • 1970-01-01
    • 2015-10-28
    • 2010-09-28
    • 1970-01-01
    相关资源
    最近更新 更多