‘super’ 是什么意思? 用Python做? - super().__init__() 和显式超类 __init__() 之间的区别

发布于 2024-07-07 20:17:35 字数 594 浏览 14 评论 0原文

之间有什么区别?

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

:和:

class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

我已经看到 super 在只有单一继承的类中被大量使用。 我可以理解为什么您会在多重继承中使用它,但不清楚在这种情况下使用它的优点是什么。


这个问题是关于技术实现细节以及访问基类__init__方法的不同方式之间的区别。 要关闭 OP 只是缺少 super 调用并询问为什么基类属性不可用的重复问题,请使用 为什么我的子类实例不包含基类的属性(当我尝试使用它们时导致 AttributeError)? 相反。

What's the difference between:

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

and:

class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

I've seen super being used quite a lot in classes with only single inheritance. I can see why you'd use it in multiple inheritance but am unclear as to what the advantages are of using it in this kind of situation.


This question is about technical implementation details and the distinction between different ways of accessing the base class __init__ method. To close duplicate questions where OP is simply missing a super call and is asking why base class attributes aren't available, please use Why don't my subclass instances contain the attributes from the base class (causing an AttributeError when I try to use them)? instead.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

彩虹直至黑白 2024-07-14 20:17:35

有什么不同?

SomeBaseClass.__init__(self) 

意思是调用SomeBaseClass__init__。 while

super().__init__()

表示从父类调用绑定的 __init__,该父类遵循实例的方法解析顺序 (MRO) 中 SomeBaseClass 的子类(定义此方法的子类)。

如果实例是这个子类的子类,则 MRO 中接下来可能有不同的父类。

简单解释

当您编写一个类时,您希望其他类能够使用它。 super() 使其他类更容易使用您正在编写的类。

正如 Bob Martin 所说,良好的架构可以让您尽可能推迟决策。

super() 可以实现这种架构。

当另一个类继承您编写的类时,它也可以从其他类继承。 根据方法解析的类的顺序,这些类可能有一个位于 __init__ 之后的 __init__

如果没有 super,您可能会硬编码您正在编写的类的父级(就像示例中那样)。 这意味着您不会调用 MRO 中的下一个 __init__ ,因此您将无法重用其中的代码。

如果您编写自己的代码供个人使用,您可能不关心这种区别。 但是,如果您希望其他人使用您的代码,那么使用 super 可以为代码用户提供更大的灵活性。

Python 2 与 3

这适用于 Python 2 和 3:

super(Child, self).__init__()

这只适用于 Python 3:

super().__init__()

它通过在堆栈帧中向上移动并获取方法的第一个参数(通常是 self )来在没有参数的情况下工作实例方法或类方法的 cls - 但也可以是其他名称)并在自由变量中查找类(例如 Child)(使用名称进行查找) __class__ 作为方法中的自由闭包变量)。

我以前更喜欢演示使用 super 的交叉兼容方式,但现在 Python 2 已基本弃用,我将演示 Python 3 的处理方式,即调用 super 没有参数。

具有前向兼容性的间接

它会给您带来什么? 对于单一继承,从静态分析的角度来看,问题中的示例实际上是相同的。 但是,使用 super 为您提供了一个具有前向兼容性的间接层。

前向兼容性对于经验丰富的开发人员来说非常重要。 您希望您的代码在更改时能够以最小的更改继续工作。 当您查看修订历史记录时,您希望准确了解何时更改了哪些内容。

您可以从单一继承开始,但是如果您决定添加另一个基类,您只需更改基类的行 - 如果您继承的类中的基类发生变化(例如添加了 mixin),您就需要更改这堂课什么也没有。

在 Python 2 中,获取 super 的参数和正确的方法参数可能会有点混乱,因此我建议使用仅 Python 3 的调用方法。

如果您知道自己在单继承中正确使用了 super,那么接下来的调试就会变得更加困难。

依赖注入

其他人可以使用您的代码并将父级注入到方法解析中:

class SomeBaseClass(object):
    def __init__(self):
        print('SomeBaseClass.__init__(self) called')
    
class UnsuperChild(SomeBaseClass):
    def __init__(self):
        print('UnsuperChild.__init__(self) called')
        SomeBaseClass.__init__(self)
    
class SuperChild(SomeBaseClass):
    def __init__(self):
        print('SuperChild.__init__(self) called')
        super().__init__()

假设您向对象添加另一个类,并且想要在 Foo 和 Bar 之间注入一个类(用于测试或其他原因):

class InjectMe(SomeBaseClass):
    def __init__(self):
        print('InjectMe.__init__(self) called')
        super().__init__()

class UnsuperInjector(UnsuperChild, InjectMe): pass

class SuperInjector(SuperChild, InjectMe): pass

使用非超级子级无法注入依赖项,因为您使用的子项已硬编码要在其自己之后调用的方法:

>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called

但是,具有使用 super 的子项的类可以正确注入依赖项

>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called

:评论

为什么这会有用?

Python 通过 C3 线性化算法 对复杂的继承树进行线性化,以创建方法解析顺序 (MRO)。

我们希望按该顺序查找方法

对于在父级中定义的方法,要在没有 super 的情况下按该顺序查找下一个方法,它必须

  1. 从实例的类型中获取 mro,
  2. 查找定义该方法的类型,
  3. 并使用以下命令查找下一个类型:方法
  4. 绑定该方法并使用预期参数调用它

UnsuperChild 不应有权访问 InjectMe。 为什么结论不是“始终避免使用 super”? 我在这里缺少什么?

UnsuperChild 无法访问InjectMeUnsuperInjector 可以访问 InjectMe - 但无法从它从 UnsuperChild 继承的方法中调用该类的方法。

两个 Child 类都打算调用 MRO 中接下来出现的同名方法,这可能是它在创建时不知道的另一个类。

没有 super 的方法会硬编码其父类的方法 - 因此限制了其方法的行为,并且子类无法在调用链中注入功能。

带有 super 的具有更大的灵活性。 可以拦截方法的调用链并注入功能。

您可能不需要该功能,但代码的子类可能需要。

结论

始终使用 super 来引用父类,而不是对其进行硬编码。

您的目的是引用下一行的父类,而不是您看到的子类继承的父类。

不使用 super 可能会给代码的用户带来不必要的限制。

What's the difference?

SomeBaseClass.__init__(self) 

means to call SomeBaseClass's __init__. while

super().__init__()

means to call a bound __init__ from the parent class that follows SomeBaseClass's child class (the one that defines this method) in the instance's Method Resolution Order (MRO).

If the instance is a subclass of this child class, there may be a different parent that comes next in the MRO.

Explained simply

When you write a class, you want other classes to be able to use it. super() makes it easier for other classes to use the class you're writing.

As Bob Martin says, a good architecture allows you to postpone decision making as long as possible.

super() can enable that sort of architecture.

When another class subclasses the class you wrote, it could also be inheriting from other classes. And those classes could have an __init__ that comes after this __init__ based on the ordering of the classes for method resolution.

Without super you would likely hard-code the parent of the class you're writing (like the example does). This would mean that you would not call the next __init__ in the MRO, and you would thus not get to reuse the code in it.

If you're writing your own code for personal use, you may not care about this distinction. But if you want others to use your code, using super is one thing that allows greater flexibility for users of the code.

Python 2 versus 3

This works in Python 2 and 3:

super(Child, self).__init__()

This only works in Python 3:

super().__init__()

It works with no arguments by moving up in the stack frame and getting the first argument to the method (usually self for an instance method or cls for a class method - but could be other names) and finding the class (e.g. Child) in the free variables (it is looked up with the name __class__ as a free closure variable in the method).

I used to prefer to demonstrate the cross-compatible way of using super, but now that Python 2 is largely deprecated, I will demonstrate the Python 3 way of doing things, that is, calling super with no arguments.

Indirection with Forward Compatibility

What does it give you? For single inheritance, the examples from the question are practically identical from a static analysis point of view. However, using super gives you a layer of indirection with forward compatibility.

Forward compatibility is very important to seasoned developers. You want your code to keep working with minimal changes as you change it. When you look at your revision history, you want to see precisely what changed when.

You may start off with single inheritance, but if you decide to add another base class, you only have to change the line with the bases - if the bases change in a class you inherit from (say a mixin is added) you'd change nothing in this class.

In Python 2, getting the arguments to super and the correct method arguments right can be a little confusing, so I suggest using the Python 3 only method of calling it.

If you know you're using super correctly with single inheritance, that makes debugging less difficult going forward.

Dependency Injection

Other people can use your code and inject parents into the method resolution:

class SomeBaseClass(object):
    def __init__(self):
        print('SomeBaseClass.__init__(self) called')
    
class UnsuperChild(SomeBaseClass):
    def __init__(self):
        print('UnsuperChild.__init__(self) called')
        SomeBaseClass.__init__(self)
    
class SuperChild(SomeBaseClass):
    def __init__(self):
        print('SuperChild.__init__(self) called')
        super().__init__()

Say you add another class to your object, and want to inject a class between Foo and Bar (for testing or some other reason):

class InjectMe(SomeBaseClass):
    def __init__(self):
        print('InjectMe.__init__(self) called')
        super().__init__()

class UnsuperInjector(UnsuperChild, InjectMe): pass

class SuperInjector(SuperChild, InjectMe): pass

Using the un-super child fails to inject the dependency because the child you're using has hard-coded the method to be called after its own:

>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called

However, the class with the child that uses super can correctly inject the dependency:

>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called

Addressing a comment

Why in the world would this be useful?

Python linearizes a complicated inheritance tree via the C3 linearization algorithm to create a Method Resolution Order (MRO).

We want methods to be looked up in that order.

For a method defined in a parent to find the next one in that order without super, it would have to

  1. get the mro from the instance's type
  2. look for the type that defines the method
  3. find the next type with the method
  4. bind that method and call it with the expected arguments

The UnsuperChild should not have access to InjectMe. Why isn't the conclusion "Always avoid using super"? What am I missing here?

The UnsuperChild does not have access to InjectMe. It is the UnsuperInjector that has access to InjectMe - and yet cannot call that class's method from the method it inherits from UnsuperChild.

Both Child classes intend to call a method by the same name that comes next in the MRO, which might be another class it was not aware of when it was created.

The one without super hard-codes its parent's method - thus is has restricted the behavior of its method, and subclasses cannot inject functionality in the call chain.

The one with super has greater flexibility. The call chain for the methods can be intercepted and functionality injected.

You may not need that functionality, but subclassers of your code may.

Conclusion

Always use super to reference the parent class instead of hard-coding it.

What you intend is to reference the parent class that is next-in-line, not specifically the one you see the child inheriting from.

Not using super can put unnecessary constraints on users of your code.

<逆流佳人身旁 2024-07-14 20:17:35

单继承中 super() 的好处是微乎其微的——大多数情况下,您不必将基类的名称硬编码到使用其父方法的每个方法中。

但是,如果没有 super(),则几乎不可能使用多重继承。 这包括常见的习惯用法,如 mixins、接口、抽象类等。这会扩展到稍后扩展您的代码。 如果后来有人想编写一个扩展 Child 和 mixin 的类,他们的代码将无法正常工作。

The benefits of super() in single-inheritance are minimal -- mostly, you don't have to hard-code the name of the base class into every method that uses its parent methods.

However, it's almost impossible to use multiple-inheritance without super(). This includes common idioms like mixins, interfaces, abstract classes, etc. This extends to code that later extends yours. If somebody later wanted to write a class that extended Child and a mixin, their code would not work properly.

久随 2024-07-14 20:17:35

我玩了一下 super(),并且认识到我们可以更改调用顺序。

例如,我们有下一个层次结构:

    A
   / \
  B   C
   \ /
    D

在这种情况下 D 的 MRO 将是(仅适用于 Python 3):

In [26]: D.__mro__
Out[26]: (__main__.D, __main__.B, __main__.C, __main__.A, object)

让我们创建一个类其中 super() 在方法执行后调用。

In [23]: class A(object): #  or with Python 3 can define class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          print("I'm from B")
...:          super().__init__()
...:   
...: class C(A):
...:      def __init__(self):
...:          print("I'm from C")
...:          super().__init__()
...:  
...: class D(B, C):
...:      def __init__(self):
...:          print("I'm from D")
...:          super().__init__()
...: d = D()
...:
I'm from D
I'm from B
I'm from C
I'm from A

    A
   / ⇖
  B ⇒ C
   ⇖ /
    D

所以我们可以看到解析顺序与 MRO 中的相同。 但是当我们在方法的开头调用 super() 时:

In [21]: class A(object):  # or class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          super().__init__()  # or super(B, self).__init_()
...:          print("I'm from B")
...:   
...: class C(A):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from C")
...:  
...: class D(B, C):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from D")
...: d = D()
...: 
I'm from A
I'm from C
I'm from B
I'm from D

我们有不同的顺序,它与 MRO 元组的顺序相反。

    A
   / ⇘
  B ⇐ C
   ⇘ /
    D 

对于其他阅读,我会推荐下一个答案:

  1. 具有超级(大层次结构)的 C3 线性化示例
  2. 新旧样式类之间的重要行为变化
  3. 新式类的内幕

I had played a bit with super(), and had recognized that we can change calling order.

For example, we have next hierarchy structure:

    A
   / \
  B   C
   \ /
    D

In this case MRO of D will be (only for Python 3):

In [26]: D.__mro__
Out[26]: (__main__.D, __main__.B, __main__.C, __main__.A, object)

Let's create a class where super() calls after method execution.

In [23]: class A(object): #  or with Python 3 can define class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          print("I'm from B")
...:          super().__init__()
...:   
...: class C(A):
...:      def __init__(self):
...:          print("I'm from C")
...:          super().__init__()
...:  
...: class D(B, C):
...:      def __init__(self):
...:          print("I'm from D")
...:          super().__init__()
...: d = D()
...:
I'm from D
I'm from B
I'm from C
I'm from A

    A
   / ⇖
  B ⇒ C
   ⇖ /
    D

So we can see that resolution order is same as in MRO. But when we call super() in the beginning of the method:

In [21]: class A(object):  # or class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          super().__init__()  # or super(B, self).__init_()
...:          print("I'm from B")
...:   
...: class C(A):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from C")
...:  
...: class D(B, C):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from D")
...: d = D()
...: 
I'm from A
I'm from C
I'm from B
I'm from D

We have a different order it is reversed a order of the MRO tuple.

    A
   / ⇘
  B ⇐ C
   ⇘ /
    D 

For additional reading I would recommend next answers:

  1. C3 linearization example with super (a large hierarchy)
  2. Important behavior changes between old and new style classes
  3. The Inside Story on New-Style Classes
伏妖词 2024-07-14 20:17:35

这一切不是都假设基类是新式类吗?

class A:
    def __init__(self):
        print("A.__init__()")

class B(A):
    def __init__(self):
        print("B.__init__()")
        super(B, self).__init__()

在Python 2中不起作用。class A必须是new-style,即:class A(object)

Doesn't all of this assume that the base class is a new-style class?

class A:
    def __init__(self):
        print("A.__init__()")

class B(A):
    def __init__(self):
        print("B.__init__()")
        super(B, self).__init__()

Will not work in Python 2. class A must be new-style, i.e: class A(object)

始于初秋 2024-07-14 20:17:35

当调用 super() 解析为类方法、实例方法或静态方法的父级版本时,我们希望将我们所在范围的当前类作为第一个参数传递,以指示哪个父级的范围我们试图解析感兴趣的对象作为第二个参数,以指示我们尝试将该范围应用于哪个对象。

考虑一个类层次结构 ABC,其中每个类都是其后一个类的父类,并且 a、bc 各自的实例。

super(B, b) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to b, as if b was an instance of A

super(C, c) 
# resolves to the scope of C's parent i.e. B
# and applies that scope to c

super(B, c) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to c

super 与静态方法一起使用

,例如 在 __new__() 方法中使用 super()

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return super(A, cls).__new__(cls, *a, **kw)

解释:

1- 尽管 __new__() 通常将其作为第一个参数对调用类的引用,它在 Python 中不是作为类方法实现的,而是作为静态方法实现的。 也就是说,直接调用 __new__() 时,必须显式传递对类的引用作为第一个参数:

# if you defined this
class A(object):
    def __new__(cls):
        pass

# calling this would raise a TypeError due to the missing argument
A.__new__()

# whereas this would be fine
A.__new__(A)

2- 调用 super() 获取父级时class 我们传递子类 A 作为其第一个参数,然后传递对感兴趣对象的引用,在本例中,它是在 A.__new__(cls) 时传递的类引用 被调用。 在大多数情况下,它也恰好是对子类的引用。 在某些情况下可能不是,例如在多代继承的情况下。

super(A, cls)

3- 由于一般规则 __new__() 是静态方法,因此 super(A, cls).__new__ 也将返回静态方法,并且需要显式提供所有参数,包括对感兴趣的对象的引用,在本例中是 cls

super(A, cls).__new__(cls, *a, **kw)

4-在没有 super 的情况下做同样的事情

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return object.__new__(cls, *a, **kw)

super 与实例方法一起使用

,例如 在 __init__() 中使用 super()

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        super(A, self).__init__(*a, **kw)

说明:

1- __init__ 是一个实例方法,这意味着它采用一个引用作为其第一个参数到一个实例。 当直接从实例调用时,引用是隐式传递的,也就是说您不需要指定它:

# you try calling `__init__()` from the class without specifying an instance
# and a TypeError is raised due to the expected but missing reference
A.__init__() # TypeError ...

# you create an instance
a = A()

# you call `__init__()` from that instance and it works
a.__init__()

# you can also call `__init__()` with the class and explicitly pass the instance 
A.__init__(a)

2-当在 __init__() 中调用 super() 时,我们将子类作为第一个参数传递,将感兴趣的对象作为第二个参数传递,该对象通常是对子类实例的引用。

super(A, self)

3- 调用 super(A, self) 返回一个代理,该代理将解析范围并将其应用于 self,就好像它现在是父类的实例一样。 我们将该代理称为s。 由于 __init__() 是一个实例方法,因此调用 s.__init__(...) 将隐式传递 self 的引用作为第一个参数到父级的 __init__()

4- 要在没有 super 的情况下执行相同的操作,我们需要将对实例的引用显式传递给父版本的 __init__()

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        object.__init__(self, *a, **kw)

super 与类方法一起使用

class A(object):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        print "A.alternate_constructor called"
        return cls(*a, **kw)

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return super(B, cls).alternate_constructor(*a, **kw)

说明:

1- 可以从类中直接调用类方法,并将对该类的引用作为其第一个参数。

# calling directly from the class is fine,
# a reference to the class is passed implicitly
a = A.alternate_constructor()
b = B.alternate_constructor()

2-当在类方法中调用 super() 来解析其父类的版本时,我们希望将当前子类作为第一个参数传递,以指示我们尝试解析到哪个父类的范围,以及感兴趣的对象作为第二个参数,指示我们想要将该范围应用于哪个对象,该对象通常是对子类本身或其子类之一的引用。

super(B, cls_or_subcls)

3- 调用 super(B, cls) 解析为 A 的范围并将其应用于 cls。 由于 alternate_constructor() 是一个类方法,因此调用 super(B, cls).alternate_constructor(...) 将隐式传递 cls 的引用作为 Aalternate_constructor() 版本的第一个参数

super(B, cls).alternate_constructor()

4- 要在不使用 super() 的情况下执行相同操作,您需要获取对A.alternate_constructor()未绑定版本的引用(即函数的显式版本)。 简单地这样做是行不通的:

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return A.alternate_constructor(cls, *a, **kw)

上面的方法行不通,因为 A.alternate_constructor() 方法将对 A 的隐式引用作为其第一个参数。 此处传递的 cls 将是其第二个参数。

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        # first we get a reference to the unbound 
        # `A.alternate_constructor` function 
        unbound_func = A.alternate_constructor.im_func
        # now we call it and pass our own `cls` as its first argument
        return unbound_func(cls, *a, **kw)

When calling super() to resolve to a parent's version of a classmethod, instance method, or staticmethod, we want to pass the current class whose scope we are in as the first argument, to indicate which parent's scope we're trying to resolve to, and as a second argument the object of interest to indicate which object we're trying to apply that scope to.

Consider a class hierarchy A, B, and C where each class is the parent of the one following it, and a, b, and c respective instances of each.

super(B, b) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to b, as if b was an instance of A

super(C, c) 
# resolves to the scope of C's parent i.e. B
# and applies that scope to c

super(B, c) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to c

Using super with a staticmethod

e.g. using super() from within the __new__() method

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return super(A, cls).__new__(cls, *a, **kw)

Explanation:

1- even though it's usual for __new__() to take as its first param a reference to the calling class, it is not implemented in Python as a classmethod, but rather a staticmethod. That is, a reference to a class has to be passed explicitly as the first argument when calling __new__() directly:

# if you defined this
class A(object):
    def __new__(cls):
        pass

# calling this would raise a TypeError due to the missing argument
A.__new__()

# whereas this would be fine
A.__new__(A)

2- when calling super() to get to the parent class we pass the child class A as its first argument, then we pass a reference to the object of interest, in this case it's the class reference that was passed when A.__new__(cls) was called. In most cases it also happens to be a reference to the child class. In some situations it might not be, for instance in the case of multiple generation inheritances.

super(A, cls)

3- since as a general rule __new__() is a staticmethod, super(A, cls).__new__ will also return a staticmethod and needs to be supplied all arguments explicitly, including the reference to the object of insterest, in this case cls.

super(A, cls).__new__(cls, *a, **kw)

4- doing the same thing without super

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return object.__new__(cls, *a, **kw)

Using super with an instance method

e.g. using super() from within __init__()

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        super(A, self).__init__(*a, **kw)

Explanation:

1- __init__ is an instance method, meaning that it takes as its first argument a reference to an instance. When called directly from the instance, the reference is passed implicitly, that is you don't need to specify it:

# you try calling `__init__()` from the class without specifying an instance
# and a TypeError is raised due to the expected but missing reference
A.__init__() # TypeError ...

# you create an instance
a = A()

# you call `__init__()` from that instance and it works
a.__init__()

# you can also call `__init__()` with the class and explicitly pass the instance 
A.__init__(a)

2- when calling super() within __init__() we pass the child class as the first argument and the object of interest as a second argument, which in general is a reference to an instance of the child class.

super(A, self)

3- The call super(A, self) returns a proxy that will resolve the scope and apply it to self as if it's now an instance of the parent class. Let's call that proxy s. Since __init__() is an instance method the call s.__init__(...) will implicitly pass a reference of self as the first argument to the parent's __init__().

4- to do the same without super we need to pass a reference to an instance explicitly to the parent's version of __init__().

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        object.__init__(self, *a, **kw)

Using super with a classmethod

class A(object):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        print "A.alternate_constructor called"
        return cls(*a, **kw)

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return super(B, cls).alternate_constructor(*a, **kw)

Explanation:

1- A classmethod can be called from the class directly and takes as its first parameter a reference to the class.

# calling directly from the class is fine,
# a reference to the class is passed implicitly
a = A.alternate_constructor()
b = B.alternate_constructor()

2- when calling super() within a classmethod to resolve to its parent's version of it, we want to pass the current child class as the first argument to indicate which parent's scope we're trying to resolve to, and the object of interest as the second argument to indicate which object we want to apply that scope to, which in general is a reference to the child class itself or one of its subclasses.

super(B, cls_or_subcls)

3- The call super(B, cls) resolves to the scope of A and applies it to cls. Since alternate_constructor() is a classmethod the call super(B, cls).alternate_constructor(...) will implicitly pass a reference of cls as the first argument to A's version of alternate_constructor()

super(B, cls).alternate_constructor()

4- to do the same without using super() you would need to get a reference to the unbound version of A.alternate_constructor() (i.e. the explicit version of the function). Simply doing this would not work:

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return A.alternate_constructor(cls, *a, **kw)

The above would not work because the A.alternate_constructor() method takes an implicit reference to A as its first argument. The cls being passed here would be its second argument.

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        # first we get a reference to the unbound 
        # `A.alternate_constructor` function 
        unbound_func = A.alternate_constructor.im_func
        # now we call it and pass our own `cls` as its first argument
        return unbound_func(cls, *a, **kw)
ぃ双果 2024-07-14 20:17:35

Super() 简而言之

  • 每个 Python 实例都有一个创建它的类。
  • Python 中的每个类都有一个祖先类链。
  • 使用 super() 的方法将工作委托给实例类链中的下一个祖先。

示例

这个小示例涵盖了所有有趣的情况:

class A:
    def m(self):
        print('A')

class B(A):
    def m(self):
        print('B start')
        super().m()
        print('B end')
        
class C(A):
    def m(self):
        print('C start')
        super().m()
        print('C end')

class D(B, C):
    def m(self):
        print('D start')
        super().m()
        print('D end')

调用的确切顺序由调用方法的实例决定:

>>> a = A()
>>> b = B()
>>> c = C()
>>> d = D()

例如 a,没有超级调用:

>>> a.m()
A

例如 b< /em>,祖先链为B -> A-> object

>>> type(b).__mro__   
(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

>>> b.m()
B start
A
B end

例如c,祖先链是C -> A-> object

>>> type(c).__mro__   
(<class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

>>> c.m()
C start
A
C end

例如d,祖先链更有趣D -> B-> C-> A-> objectmro 代表方法解析顺序):

>>> type(d).__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

>>> d.m()
D start
B start
C start
A
C end
B end
D end

更多信息

回答了“super 在 Python 中做什么?”的问题后,下一个问题是如何有效地使用它。 请参阅此分步教程或此45 分钟视频

Super() in a nutshell

  • Every Python instance has a class that created it.
  • Every class in Python has a chain of ancestor classes.
  • A method using super() delegates work to the next ancestor in the chain for the instance's class.

Example

This small example covers all the interesting cases:

class A:
    def m(self):
        print('A')

class B(A):
    def m(self):
        print('B start')
        super().m()
        print('B end')
        
class C(A):
    def m(self):
        print('C start')
        super().m()
        print('C end')

class D(B, C):
    def m(self):
        print('D start')
        super().m()
        print('D end')

The exact order of calls is determined by the instance the method is called from:

>>> a = A()
>>> b = B()
>>> c = C()
>>> d = D()

For instance a, there is no super call:

>>> a.m()
A

For instance b, the ancestor chain is B -> A -> object:

>>> type(b).__mro__   
(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

>>> b.m()
B start
A
B end

For instance c, the ancestor chain is C -> A -> object:

>>> type(c).__mro__   
(<class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

>>> c.m()
C start
A
C end

For instance d, the ancestor chain is more interesting D -> B -> C -> A -> object (mro stands for method resolution order) :

>>> type(d).__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

>>> d.m()
D start
B start
C start
A
C end
B end
D end

More information

Having answered the question of "What does super do in Python?", the next question is how to use it effectively. See this step-by-step tutorial or this 45 minute video.

听闻余生 2024-07-14 20:17:35

许多很好的答案,但对于视觉学习者来说:
首先让我们探索一下 super 的参数,然后没有。
超级继承树example

假设有一个从 Jack 类创建的实例 jack,它具有如图中绿色所示的继承链。 调用:

super(Jack, jack).method(...)

会使用jack的MRO(方法解析顺序)(其继承树按一定顺序),并将从 Jack 开始搜索。 为什么可以提供一个父类呢? 好吧,如果我们从实例 jack 开始搜索,它会找到实例方法,重点是找到它的父方法。

如果不向 super 提供参数,则就像传入的第一个参数是 self 的类,传入的第二个参数是 self 。 这些是在 Python3 中自动为您计算的。

然而,假设我们不想使用 Jack 的方法,我们可以传入 Jen 来开始向上搜索,而不是传入 Jack来自 Jen 的方法。

它一次搜索一层(宽度而不是深度),例如,如果 AdamSue 都具有所需的方法,则来自 Sue 的方法将首先被找到。

如果 CainSue 都有所需的方法,则首先调用 Cain 的方法。
这在代码中对应于:

Class Jen(Cain, Sue):

MRO是从左到右。

Many great answers, but for visual learners:
Firstly lets explore with arguments to super, and then without.
super inheritance tree example

Imagine theres an instance jack created from the class Jack, who has the inheritance chain as shown in green in the picture. Calling:

super(Jack, jack).method(...)

will use the MRO (Method Resolution Order) of jack (its inheritance tree in a certain order), and will start searching from Jack. Why can one provide a parent class? Well if we start searching from the instance jack, it would find the instance method, the whole point is to find its parents method.

If one does not supply arguments to super, its like the first argument passed in is the class of self, and the second argument passed in is self. These are auto-calculated for you in Python3.

However say we dont want to use Jack's method, instead of passing in Jack, we could of passed in Jen to start searching upwards for the method from Jen.

It searches one layer at a time (width not depth), e.g. if Adam and Sue both have the required method, the one from Sue will be found first.

If Cain and Sue both had the required method, Cain's method would be called first.
This corresponds in code to:

Class Jen(Cain, Sue):

MRO is from left to right.

清引 2024-07-14 20:17:35

在多重继承的情况下,您通常需要调用两个父级的初始化程序,而不仅仅是第一个。 super() 并不总是使用基类,而是查找方法解析顺序 (MRO) 中的下一个类,并将当前对象作为该类的实例返回。 例如:

class Base(object):
    def __init__(self):
        print("initializing Base")

class ChildA(Base):
    def __init__(self):
        print("initializing ChildA")
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        print("initializing ChildB")
        super().__init__()

class Grandchild(ChildA, ChildB):
    def __init__(self):
        print("initializing Grandchild")
        super().__init__()
        
Grandchild()

Base.__init__(self)替换

initializing Grandchild
initializing ChildA
initializing Base

super().__init__() 会产生

initializing Grandchild
initializing ChildA
initializing ChildB
initializing Base

所需的结果。

In the case of multiple inheritance, you normally want to call the initializers of both parents, not just the first. Instead of always using the base class, super() finds the class that is next in Method Resolution Order (MRO), and returns the current object as an instance of that class. For example:

class Base(object):
    def __init__(self):
        print("initializing Base")

class ChildA(Base):
    def __init__(self):
        print("initializing ChildA")
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        print("initializing ChildB")
        super().__init__()

class Grandchild(ChildA, ChildB):
    def __init__(self):
        print("initializing Grandchild")
        super().__init__()
        
Grandchild()

results in

initializing Grandchild
initializing ChildA
initializing Base

Replacing Base.__init__(self) with super().__init__() results in

initializing Grandchild
initializing ChildA
initializing ChildB
initializing Base

as desired.

汹涌人海 2024-07-14 20:17:35

这里有一些很好的答案,但它们没有解决在层次结构中的不同类具有不同签名的情况下如何使用 super() 的问题......特别是在 __init__ 的情况下>

要回答该部分并能够有效地使用 super() 我建议阅读我的答案 super() 并更改协作方法的签名

这只是这种情况的解决方案:

  1. 层次结构中的顶级类必须继承自自定义类,例如 SuperObject
  2. 如果类可以采用不同的参数,请始终将收到的所有参数作为关键字参数传递给 super 函数,并且始终接受 **kwargs
class SuperObject:        
    def __init__(self, **kwargs):
        print('SuperObject')
        mro = type(self).__mro__
        assert mro[-1] is object
        if mro[-2] is not SuperObject:
            raise TypeError(
                'all top-level classes in this hierarchy must inherit from SuperObject',
                'the last class in the MRO should be SuperObject',
                f'mro={[cls.__name__ for cls in mro]}'
            )

        # super().__init__ is guaranteed to be object.__init__        
        init = super().__init__
        init()

用法示例:

class A(SuperObject):
    def __init__(self, **kwargs):
        print("A")
        super(A, self).__init__(**kwargs)

class B(SuperObject):
    def __init__(self, **kwargs):
        print("B")
        super(B, self).__init__(**kwargs)

class C(A):
    def __init__(self, age, **kwargs):
        print("C",f"age={age}")
        super(C, self).__init__(age=age, **kwargs)

class D(B):
    def __init__(self, name, **kwargs):
        print("D", f"name={name}")
        super(D, self).__init__(name=name, **kwargs)

class E(C,D):
    def __init__(self, name, age, *args, **kwargs):
        print( "E", f"name={name}", f"age={age}")
        super(E, self).__init__(name=name, age=age, *args, **kwargs)

E(name='python', age=28)

输出:

E name=python age=28
C age=28
A
D name=python
B
SuperObject

some great answers here, but they do not tackle how to use super() in the case where different classes in the hierarchy have different signatures ... especially in the case of __init__

to answer that part and to be able to effectively use super() i'd suggest reading my answer super() and changing the signature of cooperative methods.

here's just the solution to this scenario:

  1. the top-level classes in your hierarchy must inherit from a custom class like SuperObject:
  2. if classes can take differing arguments, always pass all arguments you received on to the super function as keyword arguments, and, always accept **kwargs.
class SuperObject:        
    def __init__(self, **kwargs):
        print('SuperObject')
        mro = type(self).__mro__
        assert mro[-1] is object
        if mro[-2] is not SuperObject:
            raise TypeError(
                'all top-level classes in this hierarchy must inherit from SuperObject',
                'the last class in the MRO should be SuperObject',
                f'mro={[cls.__name__ for cls in mro]}'
            )

        # super().__init__ is guaranteed to be object.__init__        
        init = super().__init__
        init()

example usage:

class A(SuperObject):
    def __init__(self, **kwargs):
        print("A")
        super(A, self).__init__(**kwargs)

class B(SuperObject):
    def __init__(self, **kwargs):
        print("B")
        super(B, self).__init__(**kwargs)

class C(A):
    def __init__(self, age, **kwargs):
        print("C",f"age={age}")
        super(C, self).__init__(age=age, **kwargs)

class D(B):
    def __init__(self, name, **kwargs):
        print("D", f"name={name}")
        super(D, self).__init__(name=name, **kwargs)

class E(C,D):
    def __init__(self, name, age, *args, **kwargs):
        print( "E", f"name={name}", f"age={age}")
        super(E, self).__init__(name=name, age=age, *args, **kwargs)

E(name='python', age=28)

output:

E name=python age=28
C age=28
A
D name=python
B
SuperObject
爱本泡沫多脆弱 2024-07-14 20:17:35

考虑以下代码:

class X():
    def __init__(self):
        print("X")

class Y(X):
    def __init__(self):
        # X.__init__(self)
        super(Y, self).__init__()
        print("Y")

class P(X):
    def __init__(self):
        super(P, self).__init__()
        print("P")

class Q(Y, P):
    def __init__(self):
        super(Q, self).__init__()
        print("Q")

Q()

如果将 Y 的构造函数更改为 X.__init__,您将得到:

X
Y
Q

但是使用 super(Y, self).__init__(),你会得到:

X
P
Y
Q

并且 PQ 甚至可能涉及到另一个文件,而你在编写 X 时并不知道这个文件,并且Y。 因此,基本上,当您编写 class Y(X) 时,您不会知道 super(Child, self) 将引用什么,即使 Y 的签名如下简单如Y(X)。 这就是为什么 super 可能是更好的选择。

Consider the following code:

class X():
    def __init__(self):
        print("X")

class Y(X):
    def __init__(self):
        # X.__init__(self)
        super(Y, self).__init__()
        print("Y")

class P(X):
    def __init__(self):
        super(P, self).__init__()
        print("P")

class Q(Y, P):
    def __init__(self):
        super(Q, self).__init__()
        print("Q")

Q()

If change constructor of Y to X.__init__, you will get:

X
Y
Q

But using super(Y, self).__init__(), you will get:

X
P
Y
Q

And P or Q may even be involved from another file which you don't know when you writing X and Y. So, basically, you won't know what super(Child, self) will reference to when you are writing class Y(X), even the signature of Y is as simple as Y(X). That's why super could be a better choice.

为人所爱 2024-07-14 20:17:35
class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

这很容易理解。

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

好的,如果您使用 super(Child,self) 现在会发生什么?

当一个Child实例被创建时,它的MRO(方法解析顺序)根据继承的顺序是(Child,SomeBaseClass,object)的顺序。 (假设 SomeBaseClass 除了默认对象之外没有其他父对象)

通过传递 Child, selfsuperself 的 MRO 中搜索实例,并返回 Child 的下一个代理对象,在本例中为 SomeBaseClass,该对象然后调用 SomeBaseClass 的 __init__ 方法。 换句话说,如果是 super(SomeBaseClass,self),则 super 返回的代理对象将是 object

对于多重继承,MRO 可以包含许多类,因此基本上 super 可以让您决定在 MRO 中从哪里开始搜索。

class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

This is fairly easy to understand.

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

Ok, what happens now if you use super(Child,self)?

When a Child instance is created, its MRO(Method Resolution Order) is in the order of (Child, SomeBaseClass, object) based on the inheritance. (assume SomeBaseClass doesn't have other parents except for the default object)

By passing Child, self, super searches in the MRO of the self instance, and return the proxy object next of Child, in this case it's SomeBaseClass, this object then invokes the __init__ method of SomeBaseClass. In other word, if it's super(SomeBaseClass,self), the proxy object that super returns would be object

For multi inheritance, the MRO could contain many classes, so basically super lets you decide where you want to start searching in the MRO.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文