简体   繁体   English

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

[英]initialization order in python class hierarchies

In C++, given a class hierarchy, the most derived class's ctor calls its base class ctor which then initialized the base part of the object, before the derived part is instantiated.在 C++ 中,给定一个类层次结构,最派生类的 ctor 调用其基类 ctor,然后在派生部分被实例化之前初始化对象的基类部分。 In Python I want to understand what's going on in a case where I have the requirement, that Derived subclasses a given class Base which takes a callable in its __init__ method which it then later invokes.在 Python 中,我想了解在我有要求的情况下发生了什么,即 Derived 子类化给定的 Base 类,该类在其__init__方法中接受一个可调用对象,然后它会调用该方法。 The callable features some parameters which I pass in Derived class's __init__ , which is where I also define the callable function.可调用函数具有我在派生类的__init__传递的一些参数,我也在这里定义了可调用函数。 My idea then was to pass the Derived class itself to its Base class after having defined the __call__ operator我的想法是在定义__call__运算符后将派生类本身传递给它的基类

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. Is this a pythonic way of dealing with this problem?这是处理这个问题的pythonic方式吗?
  2. What is the exact order of initialization here?这里初始化的确切顺序是什么? 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?是否需要将 super 作为__init__方法中的第一条指令调用,还是可以按照我的方式进行调用?
  3. I am confused whether it is considered good practice to use super with or without arguments in python > 3.6我很困惑在 python > 3.6 中使用带或不带参数的 super 是否被认为是一种好习惯

What is the exact order of initialization here?这里初始化的确切顺序是什么?

Well, very obviously the one you can see in your code - Base.__init__() is only called when you explicitely ask for it (with the super() call).嗯,很明显,您可以在代码中看到的那个 - Base.__init__()仅在您明确要求时才被调用(使用super()调用)。 If Base also has parents and everyone in the chain uses super() calls, the parents initializers will be invoked according to the mro .如果Base也有父级并且链中的每个人都使用super()调用,则将根据mro调用父级初始化器。

Basically, Python is a "runtime language" - except for the bytecode compilation phase, everything happens at runtime - so there's very few "black magic" going on (and much of it is actually documented and fully exposed for those who want to look under the hood or do some metaprogramming).基本上,Python 是一种“运行时语言”——除了字节码编译阶段,一切都发生在运行时——所以很少有“黑魔法”在进行(而且其中大部分实际上都被记录在案并完全公开给那些想要查看的人)引擎盖或做一些元编程)。

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?是否需要将 super 作为init方法中的第一条指令调用,还是可以按照我的方式进行调用?

You call the parent's method where you see fit for the concrete use case - you just have to beware of not using instance attributes (directly or - less obvious to spot - indirectly via a method call that depends on those attributes) before they are defined.您可以在您认为适合具体用例的地方调用父方法 - 您只需要注意在定义实例属性之前不要使用实例属性(直接或 - 不太明显 - 通过依赖于这些属性的方法调用间接)。

I am confused whether it is considered good practice to use super with or without arguments in python > 3.6我很困惑在 python > 3.6 中使用带或不带参数的 super 是否被认为是一种好习惯

If you don't need backward compatibily, use super() without params - unless you want to explicitely skip some class in the MRO, but then chances are there's something debatable with your design (but well - sometimes we can't afford to rewrite a whole code base just to avoid one very special corner case, so that's ok too as long as you understand what you're doing and why).如果您不需要向后兼容,请使用不带参数的super() - 除非您想明确地跳过 MRO 中的某个类,但是您的设计可能存在一些值得商榷的地方(但好吧 - 有时我们无法重写整个代码库只是为了避免一个非常特殊的极端情况,所以只要您了解自己在做什么以及为什么这样做也可以)。

Now with your core question:现在回答你的核心问题:

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__ is a class attribute and is shared by all instances of the class. self.__class__.__call__是一个类属性,由该类的所有实例共享。 This means that you either have to make sure you are only ever using one single instance of the class (which doesn't seem to be the goal here) or are ready to have totally random results, since each new instance will overwrite self.__class__.__call__ with it's own version.这意味着您要么必须确保只使用该类的一个实例(这似乎不是这里的目标),要么准备好获得完全随机的结果,因为每个新实例都会覆盖self.__class__.__call__使用它自己的版本。

If what you want is to have each instance's __call__ method to call it's own version of process() , then there's a much simpler solution - just make _process an instance attribute and call it from __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)

Or even simpler:或者更简单:

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)

EDIT:编辑:

Base requires a callable in ins init method. Base 需要在 ins init 方法中调用。

This would be better if your example snippet was closer to your real use case.如果您的示例代码段更接近您的实际用例,这会更好。

But when I call super().但是当我调用 super() 时。 init() the call method of Derived should not have been instantiated yet or has it? init() Derived 的调用方法不应该被实例化还是已经实例化了?

Now that's a good question... Actually, Python methods are not what you think they are.现在这是一个很好的问题...实际上,Python方法并不是您认为的那样。 What you define in a class statement's body using the def statement are still plain functions, as you can see by yourself:您使用def语句在class语句的主体中定义的内容仍然是普通函数,您可以自己看到:

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

"Methods" are only instanciated when an attribute lookup resolves to a class attribute that happens to be a function: “方法”仅在属性查找解析为恰好是函数的类属性时才会实例化:

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

(if you wonder how this happens, it's documented here ) (如果您想知道这是如何发生的,请在此处记录

and they actually are just thin wrappers around a function, instance and class (or function and class for classmethods), which delegate the call to the underlying function, injecting the instance (or class) as first argument.它们实际上只是一个函数、实例和类(或类方法的函数和类)的薄包装器,它们将调用委托给底层函数,将实例(或类)作为第一个参数注入。 In CS terms, a Python method is the partial application of a function to an instance (or class).在 CS 术语中,Python 方法是将函数部分应用于实例(或类)。

Now as I mentionned upper, Python is a runtime language, and both def and class are executable statements.现在正如我上面提到的,Python 是一种运行时语言, defclass都是可执行语句。 So by the time you define your Derived class, the class statement creating the Base class object has already been executed (else Base wouldn't exist at all), with all the class statement block being executed first (to define the functions and other class attributes).因此,当您定义Derived类时,创建Base类对象的class语句已经执行(否则Base根本不存在),首先执行所有class语句块(定义函数和其他类)属性)。

So "when you call super().__init()__ ", the __call__ function of Base HAS been instanciated (assuming it's defined in the class statement for Base of course, but that's by far the most common case).所以“当你调用super().__init()__ ”时, Base__call__函数已经被实例化(当然,假设它是在Baseclass语句中定义的,但这是迄今为止最常见的情况)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM