__init__ 子类化 dict 和其他东西时不调用

__init__ not called when subcalssing dict and something else

考虑以下代码:

class Lockable(object):

    def __init__(self):
        self._lock = None

    def is_locked(self):
       return self._lock is None


class LockableDict(dict, Lockable):
     pass

现在:

In [2]: l = example.LockableDict(a=1, b=2, c=3)

In [3]: l.is_locked()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-3-4344e3948b9c> in <module>()
----> 1 l.is_locked()

/home/sven/utils/example.py in is_locked(self)
      8 
      9     def is_locked(self):
---> 10         return self._lock is None

AttributeError: 'LockableDict' object has no attribute '_lock'

看起来 Lockable.__init__ 根本没有被调用。是这样吗?为什么?

为了让事情更有趣,事实证明,像这样更改 LockableDict class header 就足够了:

class LockableDict(Lockable, dict):

让它发挥作用。为什么?

从这条评论可以清楚地看出你的误解:

I don't override __init__ in LockableDict, so it should generate an __init__ making automatic calls to base classes' __init__s, shouldn't it?

没有!

首先,不会自动生成任何内容;方法解析发生在调用时。

并且在调用时,Python不会调用每个基class的__init__,它只会调用第一个它找到了一个。*

这就是 super 存在的原因。如果您没有在覆盖中显式调用 super,则基 classes 甚至兄弟 classes 都不会调用它们的实现。 dict.__init__ 不调用它的 super.

你的 Lockable.__init__ 也没有。所以,这意味着颠倒顺序可以确保 Lockable.__init__ 被调用……但是 dict.__init__ 现在 不会 被调用。


那么,你到底想做什么?那么,Python 的 MRO 算法旨在为 合作 classes 的层次结构提供最大的灵活性,抛出任何非合作 classes在最后。所以,你可以这样做:

class Lockable(object):

    def __init__(self):
        super().__init__() # super(Lockable, self).__init__() for 2.x
        self._lock = None

    def is_locked(self):
       return self._lock is None


class LockableDict(Lockable, dict):
     pass

另请注意,这允许您通过所有合作的 classes 沿链传递初始化参数,然后只需使用参数调用不友好的 class 的 __init__你知道它需要。

在某些情况下,您必须先放置一个不友好的 class。** 在这种情况下,您必须明确地调用它们:

class LockableDict(dict, Lockable):
    def __init__(self):
        dict.__init__(self)
        Lockable.__init__(self)

但幸运的是,这种情况并不经常出现。


* 在标准中method resolution order, which is slightly more complicated than you'd expect. The same MRO rule is applied when you call super to find the "next" class—which may be a base class, or a sibling, or even a sibling of a subclass. You may find the Wikipedia article on C3 linearization更平易近人。

** 特别是对于内置的 classes,因为它们在某些方面很特殊,不值得在这里深入探讨。再一次,许多内置 classes 实际上并没有在它们的 __init__ 中做任何事情,而是在 __new__ 构造函数中进行所有初始化。