有什么区别:

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

and:

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

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


当前回答

有很多很棒的答案,但对于视觉学习者来说: 首先让我们来探讨与参数超,然后没有。

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

超级(杰克,杰克).method(…)

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

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

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

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

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

Class Jen(Cain, Sue):

MRO从左到右。

其他回答

有很多很棒的答案,但对于视觉学习者来说: 首先让我们来探讨与参数超,然后没有。

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

超级(杰克,杰克).method(…)

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

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

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

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

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

Class Jen(Cain, Sue):

MRO从左到右。

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

考虑一个类层次结构a、B和C,其中每个类都是后面一个类的父类,并且a、B和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和static方法

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

super(A, cls)

3-因为作为一般规则__new__()是一个静态方法,super(a, cls)。__new__也将返回一个staticmethod,并且需要显式地提供所有参数,包括对感兴趣对象的引用,在本例中是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,就像它现在是父类的实例一样。因为__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)

解释:

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

# 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的引用作为第一个参数传递给a版本的alternate_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)

有什么不同?

SomeBaseClass.__init__(self) 

意思是调用SomeBaseClass的__init__。而

super().__init__()

意味着在实例的方法解析顺序(MRO)中,从SomeBaseClass的子类(定义此方法的子类)后面的父类调用绑定__init__。

如果实例是这个子类的子类,那么在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),你在这个类中什么都不会改变。

在python2中,将参数设置为super和正确的方法参数可能会让人有点困惑,所以我建议使用python3唯一的调用方法。

如果您知道您正确地使用了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的方法,则必须这样做

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

UnsuperChild不应该访问InjectMe。为什么结论不是“总是避免使用super”?我错过了什么?

UnsuperChild不能访问InjectMe。UnsuperInjector可以访问InjectMe,但不能从它继承自UnsuperChild的方法中调用该类的方法。

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

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

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

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

结论

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

您想要引用的是顺位的父类,而不是您看到的子类继承的父类。

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

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

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

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

将不能在python2中工作。类A必须是new-style,即:类A(对象)

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

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