Python中“超级”是做什么的?

有什么区别:

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

和:

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

我已经看到super被用在相当多的类只有单一的继承。 我可以看到为什么你会用它来进行多重继承,但是对于在这种情况下使用它的好处还不清楚。

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

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

    有什么不同?

     SomeBaseClass.__init__(self) 

    意味着调用SomeBaseClass__init__ 。 而

     super(Child, self).__init__() 

    意味着从实例方法解析顺序中的Child __init__的父类调用绑定的__init__

    如果实例是Child的一个子类,则在方法解析顺序中可能会有另一个父类。

    Python 2与3

    这在Python 2和3中工作:

     super(Child, self).__init__() 

    这只适用于Python 3:

     super().__init__() 

    它通过在堆栈框架中移动并获取方法的第一个参数(通常是实例方法的self或者一个类方法的cls – 但可以是其他名称)并找到类中的类(例如Child )自由变量(它在名称__class__作为方法中的自由闭包变量)。

    我更愿意演示使用super的交叉兼容方式,但是如果只使用Python 3,则可以不带参数地调用它。

    具有向前兼容性的间接性

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

    向前兼容对于经验丰富的开发人员非常重要。 您希望自己的代码在更改时能够保持最小的更改。 当你看你的修订历史,你想看看什么时候改变。

    你可以从单一的继承开始,但是如果你决定添加另一个基类,你只需要改变这个基础 – 如果基类在你继承的类中改变(比如添加了一个mixin),你会改变这课没有什么 特别是在Python 2中,将参数取得超级以及正确的方法参数是很困难的。 如果你知道你正在使用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 

    结论

    总是使用super引用父类。

    你打算引用下一个在线的父类,而不是专门看到孩子继承的父类。

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

    这并不是所有这些假定基类是一个新式的类吗?

     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)

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

    考虑一个类层次结构ABC ,其中每个类是它后面的父类,以及每个类的abc各自的实例。

     super(B, b) # resolves to the scope of B's parent ie 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 ie B # and applies that scope to c super(B, c) # resolves to the scope of B's parent ie 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) 

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

     super(A, cls) 

    3-因为通常__new__()是一个静态方法, super(A, cls).__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() __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-在__init__() super()内调用super() ,我们将子类作为第一个参数,将感兴趣的对象作为第二个参数传递,通常是对子类实例的引用。

     super(A, self) 

    3- super(A, self)调用super(A, self)返回一个代理,它将解析范围并将其应用于self ,就好像它现在是父类的实例一样。 我们称之为代理服务器。 由于__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() 

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

     super(B, cls_or_subcls) 

    3- super(B, cls)呼叫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()未绑定版本(即函数的显式版本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()方法将隐式引用作为其第一个参数。 在这里传递的是第二个参数。

     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) 

    我已经玩了一下super() ,并认识到我们可以改变呼叫顺序。

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

      A / \ BC \ / 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. 新式的内幕