首页 > 解决方案 > python类层次结构中的初始化顺序

问题描述

在 C++ 中,给定类层次结构,最派生类的 ctor 调用其基类 ctor,然后在派生部分实例化之前初始化对象的基类部分。在 Python 中,我想了解在我有要求的情况下发生了什么,即 Derived 子类化一个给定的类 Base,该类在其__init__方法中采用一个可调用对象,然后再调用该类。可调用具有一些我在派生类中传递的参数__init__,这也是我定义可调用函数的地方。__call__然后我的想法是在定义运算符后将 Derived 类本身传递给它的 Base 类

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 中使用带或不带参数的 super 是否被认为是一种好习惯

标签: pythoninitializationsuper

解决方案


这里初始化的确切顺序是什么?

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

基本上,Python 是一种“运行时语言”——除了字节码编译阶段,一切都发生在运行时——所以很少有“黑魔法”发生(其中大部分实际上已经记录在案并完全公开给那些想要查看的人引擎盖或做一些元编程)。

是否需要调用 super 作为init方法中的第一条指令,还是可以按照我的方式进行操作?

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

我很困惑在 python > 3.6 中使用带或不带参数的 super 是否被认为是一种好习惯

如果您不需要向后兼容,请super()不带参数使用 - 除非您想明确跳过 MRO 中的某些类,但是您的设计可能存在争议(但好吧 - 有时我们负担不起重写整个代码base 只是为了避免一个非常特殊的极端情况,所以只要你了解你在做什么以及为什么这样做也可以)。

现在你的核心问题:

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__是一个类属性,由该类的所有实例共享。这意味着您要么必须确保只使用该类的一个实例(这似乎不是这里的目标),要么准备好获得完全随机的结果,因为每个新实例都会被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 需要 ins init 方法中的可调用。

如果您的示例代码段更接近您的实际用例,这会更好。

但是当我调用 super() 时。init() Derived 的调用方法不应该被实例化还是已经实例化了?

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

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

“方法”仅在属性查找解析为恰好是函数的类属性时才被实例化:

Foo().bar main.Foo 对象在 0x7f3cef4de908>> Foo().bar main.Foo 对象在 0x7f3cef4de940>>

(如果您想知道这是如何发生的,请在此处记录

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

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

所以“当你调用super().__init()__”时,HAS 的__call__ 功能Base被实例化了(当然,假设它是在classfor 语句中定义的Base,但这是迄今为止最常见的情况)。


推荐阅读