“超级”在Python中做了什么?

时间:2008-10-21 18:13:15

标签: python oop inheritance super

有什么区别:

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

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

我看到super在只有单一继承的类中被大量使用。我可以看到为什么你在多重继承中使用它,但不清楚在这种情况下使用它有什么好处。

11 个答案:

答案 0 :(得分:279)

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

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

答案 1 :(得分:260)

有什么区别?

SomeBaseClass.__init__(self) 

表示致电SomeBaseClass' __init__。而

super(Child, self).__init__()

表示从实例的方法解析顺序(MRO)中__init__后面的父类调用绑定的Child

如果实例是Child的子类,则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 3,则可以不带参数调用它。

具有向前兼容性的间接

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

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

你可以从单继承开始,但是如果你决定添加另一个基类,你只需要用基数来改变这一行 - 如果基数在你继承的类中发生变化(例如添加了mixin)你和#39; d在本课程中不做任何改动。特别是在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__()

假设您在对象中添加了另一个类,并希望在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

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

>>> 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 linearization algorithm线性化复杂的继承树,以创建方法解析订单(MRO)。

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

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

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

    UnsuperChild无法访问InjectMe。为什么不能得出结论"始终避免使用super"?我在这里缺少什么?

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

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

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

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

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

    结论

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

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

    不使用{{1}}可能会对代码的用户造成不必要的限制。

答案 2 :(得分: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必须是新式的,即:class A(object)

答案 3 :(得分:27)

我曾与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 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

答案 4 :(得分:16)

当调用super()来解析父类的classmethod,instance方法或staticmethod的版本时,我们想要传递我们所在范围的当前类作为第一个参数,以指示哪个父母的范围我们正试图解决,作为第二个参数,感兴趣的对象表明我们试图将该范围应用于哪个对象。

考虑一个类层次结构ABC,其中每个类都是其后面的类的父级,ab,和c各自的实例。

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与staticmethod

一起使用

e.g。在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)

说明:

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()来到父类时,我们传递子类A作为它的第一个参数,然后我们传递对感兴趣对象的引用,在这种情况下它是&#39 ;是调用A.__new__(cls)时传递的类引用。在大多数情况下,它恰好也是对子类的引用。在某些情况下,它可能不是,例如在多代继承的情况下。

super(A, cls)

3-因为一般规则__new__()是静态方法,super(A, cls).__new__也会返回静态方法,需要明确提供所有参数,包括对insterest对象的引用,在这种情况下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与实例方法

一起使用

e.g。在super()

中使用__init__()
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-在super()内调用__init__()时,我们将子类作为第一个参数传递,将感兴趣的对象作为第二个参数传递,这通常是对子类实例的引用

super(A, self)

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

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

__init__()

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

一起使用
super

说明:

1-可以直接从类中调用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)

2-在类方法中调用# calling directly from the class is fine, # a reference to the class is passed implicitly a = A.alternate_constructor() b = B.alternate_constructor() 以解析其父级的版本时,我们希望将当前子类作为第一个参数传递,以指示我们的父级范围是哪个&# #39;重新尝试解析,并将感兴趣的对象作为第二个参数来指示我们要将该范围应用于哪个对象,这通常是对子类本身或其子类之一的引用。

super()

3-致电super(B, cls_or_subcls) 解析为super(B, cls)的范围,并将其应用于A。由于cls是一种类方法,因此调用alternate_constructor()将隐含地将super(B, cls).alternate_constructor(...)的引用作为cls A的{​​{1}}版本的第一个参数传递/ p>

alternate_constructor()

4-在不使用super(B, cls).alternate_constructor() 的情况下执行相同的操作,您需要获得对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

答案 5 :(得分:9)

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,祖先链是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'>)

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

例如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

更多信息

回答了“super 在 Python 中做了什么?”的问题,接下来的问题是如何有效地使用它。请参阅此 step-by-step tutorial 或此 45 minute video

答案 6 :(得分:3)

在多重继承的情况下,通常需要调用两个父级的初始化器,而不仅仅是第一个。 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()

产生

initializing Grandchild
initializing ChildA
initializing Base

Base.__init__(self)替换为super().__init__()会导致

initializing Grandchild
initializing ChildA
initializing ChildB
initializing Base

根据需要。

答案 7 :(得分:1)

许多很好的答案,但对于视觉学习者而言: 首先,让我们探索关于super的参数,然后再探讨。 super inheritance tree example

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

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

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

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

但是,我们不想使用Jack的方法,而不是传递Jack,我们可以传递Jen来开始从{{1 }}。

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

如果SueCain都具有必需的方法,则将首先调用Sue的方法。 这在代码中对应于:

Cain

MRO是从左到右。

答案 8 :(得分:1)

考虑以下代码:

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甚至可能与另一个在写入XY时不知道的文件有关。因此,基本上,您将不知道super(Child, self)在编写class Y(X)时将引用什么,即使Y的签名也像Y(X)一样简单。这就是为什么超级可能是更好的选择。

答案 9 :(得分:0)

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,对象)。 (假设SomeBaseClass除了默认对象外没有其他父对象)

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

对于多继承,MRO可以包含许多类,因此基本上object允许您决定要在MRO中开始搜索的位置。

答案 10 :(得分:0)

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

要回答这一部分并能够有效使用SuperObject,我建议阅读我的答案super() and changing the signature of cooperative methods

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

  
      
  1. 层次结构中的顶级类必须继承自定义类,例如**kwargs
  2.   
  3. 如果类可以采用不同的参数,则始终将接收到的所有参数作为关键字参数传递给超函数,并始终接受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()
  4.   
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

输出:

FetchError: invalid json response body at 
https://api.instagram.com/v1/explore/tags:india?access_token= reason: 
Unexpected token < in JSON at position 0