"超级"在Python中做了什么?

use*_*785 522 python oop inheritance super

有什么区别:

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()
Run Code Online (Sandbox Code Playgroud)

和:

class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)
Run Code Online (Sandbox Code Playgroud)

我已经看到super在只有单继承的类中使用了很多.我可以看到为什么你在多重继承中使用它,但不清楚在这种情况下使用它的优点是什么.

Joh*_*kin 296

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

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

  • 您能否通过“它无法正常工作”的含义提供示例? (2认同)
  • 通过避免使用“ super()”,可以防止某些扩展模式并防止对[多重继承](https://docs.python.org/3.7/tutorial/classes.html#multiple-inheritance)的轻松利用。如果您的类继承自其他三个类,那么您直接调用其“父”方法?“超级”(及其提供的[方法解析顺序](https://www.python.org/download/releases/2.3/mro/))为您解决了此问题。 (2认同)

Aar*_*all 287

有什么不同?

SomeBaseClass.__init__(self) 
Run Code Online (Sandbox Code Playgroud)

意味着调用SomeBaseClass__init__.而

super(Child, self).__init__()
Run Code Online (Sandbox Code Playgroud)

表示__init__Child实例的方法解析顺序(MRO)中的父类调用绑定.

如果实例是Child的子类,则MRO中可能会有另一个父级.

简单解释一下

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

正如Bob Martin所说,一个好的架构可以让你尽可能地推迟决策.

super() 可以启用这种架构.

当另一个类为您编写的类创建子类时,它也可以从其他类继承.并且这些类可以__init__在此之后__init__基于方法解析的类的顺序来实现.

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

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

Python 2与3

这适用于Python 2和3:

super(Child, self).__init__()
Run Code Online (Sandbox Code Playgroud)

这仅适用于Python 3:

super().__init__()
Run Code Online (Sandbox Code Playgroud)

通过在堆栈框架中向上移动并获取方法的第一个参数(通常self用于实例方法或cls类方法 - 但可以是其他名称)并Child在自由变量中查找类(例如),它不使用任何参数(它被查找为名称__class__作为方法中的自由闭包变量.

我更喜欢演示交叉兼容的使用方式super,但如果你只使用Python 3,你可以不带参数调用它.

具有前向兼容性的间接

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

前向兼容性对经验丰富的开发人员非常重要.您希望代码在更改时保持最小的更改.当您查看修订历史记录时,您希望确切地看到何时发生了变化.

你可以从单继承开始,但是如果你决定添加另一个基类,你只需要更改基数的行 - 如果你继承的类中的基数发生了变化(比如添加了mixin)你就会改变这堂课没什么.特别是在Python 2中,获取参数super和正确的方法参数可能很困难.如果您知道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(SuperChild, self).__init__()
Run Code Online (Sandbox Code Playgroud)

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

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

class UnsuperInjector(UnsuperChild, InjectMe): pass

class SuperInjector(SuperChild, InjectMe): pass
Run Code Online (Sandbox Code Playgroud)

使用un-super子节点无法注入依赖项,因为您正在使用的子节点已对其自身调用的方法进行了硬编码:

>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called
Run Code Online (Sandbox Code Playgroud)

但是,使用子级的类super可以正确地注入依赖项:

>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called
Run Code Online (Sandbox Code Playgroud)

发表评论

为什么这个世界会有用呢?

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

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

对于在父级中定义的方法,在没有的情况下找到该顺序中的下一个方法super,就必须这样做

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

UnsuperChild不该访问InjectMe.为什么不是"总是避免使用super" 的结论?我在这里错过了什么?

UnsuperChild不会访问InjectMe.它可以UnsuperInjector访问InjectMe- 但不能从它继承的方法中调用该类的方法UnsuperChild.

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

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

在一个 super具有更大的灵活性.可以拦截方法的调用链并注入功能.

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

结论

始终用于super引用父类而不是硬编码.

您打算引用的是下一行的父类,而不是您看到该子类继承的父类.

不使用super可以对代码的用户施加不必要的约束.

  • 感谢您提供这个超级详细的答案!我在其他地方找不到如何在 Python 3 语法中推导第二个参数(即“在堆栈框架中向上移动并获取方法的第一个参数”)。看起来很奇怪,他们采用了这种隐式语法:更少的输入,但与类代码中其他地方的处理方式有些不一致,在类代码中,您需要始终显式指定“self”(例如,没有隐式对象变量解析)。 (2认同)

mha*_*wke 35

是不是所有这些都假设基类是一个新式的类?

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

class B(A):
    def __init__(self):
        print("B.__init__()")
        super(B, self).__init__()
Run Code Online (Sandbox Code Playgroud)

在Python 2中不起作用.class A必须是新式的,即:class A(object)


Inf*_*ion 30

我玩了一下super(),并且认识到我们可以改变通话顺序.

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

    A
   / \
  B   C
   \ /
    D
Run Code Online (Sandbox Code Playgroud)

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

In [26]: D.__mro__
Out[26]: (__main__.D, __main__.B, __main__.C, __main__.A, object)
Run Code Online (Sandbox Code Playgroud)

让我们创建一个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
Run Code Online (Sandbox Code Playgroud)

因此我们可以看到解决方案顺序与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
Run Code Online (Sandbox Code Playgroud)

我们有一个不同的顺序,它颠倒了MRO元组的顺序.

    A
   / ?
  B ? C
   ? /
    D 
Run Code Online (Sandbox Code Playgroud)

如需更多阅读,我会建议下一个答案:

  1. 具有超级(大型层次结构)的C3线性化示例
  2. 旧样式类和新样式类之间的重要行为更改
  3. 新风格的内幕故事

  • 只要掌握了第一个,第二个就很容易理解了。它就像一个堆栈。你将 print'' 推入堆栈并执行 super() ,当它完成 A 时,它开始打印该堆栈中的内容,因此顺序是相反的。 (3认同)
  • 这就像一个递归。在第二个示例中,它首先调用所有类,将它们放入队列(或堆栈)中,因为 super() 首先被调用。然后,当它到达基类时,它执行基类的 print 方法,并进入队列中的下一个(或如 @grantsun 所说的堆栈中)。在第一个示例中,D 的 print() 首先被调用,这就是为什么它首先打印“I'm from D”,然后才转到下一个类,在那里它再次首先看到 print() ,然后才调用 super() (3认同)
  • 不好的是,我没有注意到每个类实例都是先用 super() 启动的。那么如果是这样的话,不应该是ABCD吗?我以某种方式理解 ACBD 是如何产生的,但仍然无法说服并且仍然有点困惑。我的理解是, d = D() 调用具有 2 个自参数的类 D(B,C),因为首先启动 super() 然后调用 B 及其属性,然后 D 不会在 C 之前打印,因为 Class D(B,C)包含2个自参数,所以必须执行第二个自参数,即C(A)类,执行完后就没有更多的自参数可以执行 (2认同)
  • 然后它会打印C,然后打印B,最后打印D。我说得对吗? (2认同)

Mic*_*oka 18

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

考虑一个类层次结构A,B以及C其中,每个类是一个跟随它的父,并且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
Run Code Online (Sandbox Code Playgroud)

使用super静态方法

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

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

        return super(A, cls).__new__(cls, *a, **kw)
Run Code Online (Sandbox Code Playgroud)

说明:

尽管通常__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)
Run Code Online (Sandbox Code Playgroud)

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

super(A, cls)
Run Code Online (Sandbox Code Playgroud)

3-因为一般规则__new__()是静态方法,所以super(A, cls).__new__也会返回一个静态方法,并且需要明确地提供所有参数,包括在这种情况下对insterest对象的引用cls.

super(A, cls).__new__(cls, *a, **kw)
Run Code Online (Sandbox Code Playgroud)

4-没有做同样的事情 super

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

        return object.__new__(cls, *a, **kw)
Run Code Online (Sandbox Code Playgroud)

使用super实例方法

例如super()从内部使用__init__()

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

        super(A, self).__init__(*a, **kw)
Run Code Online (Sandbox Code Playgroud)

说明:

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)
Run Code Online (Sandbox Code Playgroud)

当调用2- super()__init__()我们通过子类作为第一个参数和感兴趣作为第二个参数,这在一般是子类的实例的引用的对象.

super(A, self)
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

使用superclassmethod

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)
Run Code Online (Sandbox Code Playgroud)

说明:

1-可以直接从类中调用classmethod,并将第一个参数作为类的引用.

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

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

super(B, cls_or_subcls)
Run Code Online (Sandbox Code Playgroud)

3-呼叫super(B, cls)解决了范围A并将其应用于cls.由于alternate_constructor()是一个类方法,调用super(B, cls).alternate_constructor(...)将隐式地将引用cls作为第一个参数传递给A的版本alternate_constructor()

super(B, cls).alternate_constructor()
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

上述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)
Run Code Online (Sandbox Code Playgroud)


Ray*_*ger 12

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')
Run Code Online (Sandbox Code Playgroud)

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

>>> a = A()
>>> b = B()
>>> c = C()
>>> d = D()
Run Code Online (Sandbox Code Playgroud)

例如a,没有超级调用:

>>> a.m()
A
Run Code Online (Sandbox Code Playgroud)

例如b,祖先链是B -> A -> object

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

>>> b.m()
B start
A
B end
Run Code Online (Sandbox Code Playgroud)

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

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

>>> b.m()
C start
A
C end
Run Code Online (Sandbox Code Playgroud)

例如d,祖先链更有趣D -> B -> C -> A -> object

>>> 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
Run Code Online (Sandbox Code Playgroud)

更多信息

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

  • 如果您想知道 MRO 是如何工作的(即为什么执行从 B 跳到 C 而不是从 B 跳到 A),请检查此问题的已接受答案:/sf/ask/4531808221/ -order-mro-is-working-in-this-python-code。基本上,“super()”将方法调用委托给类型的父类或同级类。这就是 B 中的“super()”调用,将调用委托给 C(B 的兄弟)而不是 A(B 的父级)。 (3认同)

run*_*ace 9

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

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

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

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

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

但是,如果我们不想使用Jack的方法,而不是传入Jack,我们可以传入Jen以开始向上搜索 from 的方法Jen

它在一个时间(宽度不深度),例如,如果搜索一个层AdamSue两者都具有所要求的方法,从所述一个Sue将首先找到。

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

Class Jen(Cain, Sue):
Run Code Online (Sandbox Code Playgroud)

MRO 从左到右。