python class 层次结构中的初始化顺序

initialization order in python class hierarchies

在 C++ 中,给定一个 class 层次结构,派生程度最高的 class 构造函数调用其基础 class 构造函数,后者在派生之前初始化对象的基础部分部分被实例化。在 Python 中,我想了解在我有要求的情况下发生了什么,Derived subclass 是一个给定的 class Base,它在其 __init__ 中接受一个可调用稍后调用的方法。可调用函数具有一些我在 Derived class 的 __init__ 中传递的参数,这也是我定义可调用函数的地方。然后我的想法是在定义 __call__ operator

之后将 Derived class 本身传递给它的 Base class
class Derived(Base):
    def __init__(self, a, b):
        def _process(c, d):
            do_something with a and b

        self.__class__.__call__ = _process
        super(Derived, self).__init__(self)
  1. 这是处理这个问题的 pythonic 方法吗?
  2. 这里初始化的确切顺序是什么?是否需要将 super 作为 __init__ 方法中的第一条指令调用,还是可以像我那样做?
  3. 我很困惑在 python > 3.6
  4. 中使用带参数或不带参数的 super 是否被认为是好的做法

What is the exact order of initialization here?

很明显,您可以在代码中看到的那个 - Base.__init__() 仅在您明确要求时才会调用(使用 super() 调用)。如果 Base 也有父代并且链中的每个人都使用 super() 调用,则父代初始化器将根据 mro 调用。

基本上,Python 是一个 "runtime language" - 除了字节码编译阶段,一切都在运行时发生 - 所以很少 "black magic" 正在进行(实际上大部分是为那些想深入了解或进行一些元编程的人提供了文档和充分展示)。

Does one needs to call super as a first instruction in the init method or is it ok to do it the way I did?

您在您认为适合具体用例的地方调用父方法 - 您只需要注意不要使用实例属性(直接或 - 不太容易发现 - 通过依赖于这些属性的方法调用间接)在定义它们之前。

I am confused whether it is considered good practice to use super with or without arguments in python > 3.6

如果您不需要向后兼容,请使用不带参数的 super() - 除非您想明确跳过 MRO 中的某些 class,但您的设计可能存在一些值得商榷的地方(但是好吧 - 有时我们不能为了避免一个非常特殊的极端情况而重写整个代码库,所以只要你明白你在做什么以及为什么这样做也没关系。

现在回答你的核心问题:

class Derived(Base):
    def __init__(self, a, b):
        def _process(c, d):
            do_something with a and b

        self.__class__.__call__ = _process
        super(Derived, self).__init__(self)

self.__class__.__call__ 是一个 class 属性,由 class 的所有实例共享。这意味着您要么必须确保只使用 class 的一个实例(这似乎不是这里的目标),要么准备好获得完全随机的结果,因为每个新实例都会用它自己的版本覆盖 self.__class__.__call__

如果你想要的是让每个实例的 __call__ 方法调用它自己的 process() 版本,那么有一个更简单的解决方案 - 只需将 _process 设为实例属性并从 __call__ 调用它:

class Derived(Base):
    def __init__(self, a, b):
        def _process(c, d):
            do_something with a and b

        self._process = _process
        super(Derived, self).__init__(self)

   def __call__(self, c, d):
       return self._process(c, d)

或者更简单:

class Derived(Base):
    def __init__(self, a, b):
        super(Derived, self).__init__(self)
        self._a = a
        self._b = b

   def __call__(self, c, d):
       do_something_with(self._a, self._b)

编辑:

Base requires a callable in ins init method.

如果您的示例片段更接近您的实际用例,那就更好了。

But when I call super().init() the call method of Derived should not have been instantiated yet or has it?

这是一个很好的问题...实际上,Python 方法 并不是您想的那样。你在 class 语句体中使用 def 语句定义的仍然是普通函数,正如你自己看到的那样:

class Foo: ... def bar(self): pass ... Foo.bar

"Methods" 仅在属性查找解析为恰好是函数的 class 属性时实例化:

Foo().bar main.Foo object at 0x7f3cef4de908>> Foo().bar main.Foo object at 0x7f3cef4de940>>

(如果您想知道这是怎么发生的,那就是 documented here

它们实际上只是函数、实例和 class(或 class 方法的函数和 class)的薄包装,它们将调用委托给底层函数,注入实例(或 class)作为第一个参数。在 CS 术语中,Python 方法是将函数部分应用到实例(或 class)。

现在正如我上面提到的,Python是一种运行时语言,defclass都是可执行语句。因此,当您定义 Derived class 时,创建 Base class 对象的 class 语句已经执行(否则 Base 不会根本不存在),首先执行所有 class 语句块(定义函数和其他 class 属性)。

所以"when you call super().__init()__",Base__call__函数已经实例化(假设它在class语句中定义对于 Base 当然,但这是迄今为止最常见的情况)。