oop:在Python中super()的用法是什么?

有什么区别:

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

和:

1
2
3
class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

我已经看到super在只有一个继承的类中被大量使用。我知道为什么您要在多重继承中使用它,但是我不清楚在这种情况下使用它有什么好处。


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

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


有什么区别呢?

1
SomeBaseClass.__init__(self)

表示调用SomeBaseClass__init__。而

1
super(Child, self).__init__()

表示从在实例的方法解析顺序(MRO)中遵循Child的父类中调用一个绑定__init__

如果实例是子类的子类,则MRO中可能会出现另一个父类。

解释简单的

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

正如Bob Martin所说,好的架构允许您尽可能地推迟决策。

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

当另一个类子类化您编写的类时,它也可以从其他类继承。这些类可以有一个__init__,这个__init__后面跟着一个__init__,基于方法解析类的顺序。

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

如果您正在编写供个人使用的代码,那么您可能并不关心这种区别。但是如果您希望其他人使用您的代码,使用super是允许代码用户具有更大灵活性的一件事。

Python 2对3

这在python2和python3中工作:

1
super(Child, self).__init__()

这只适用于python3:

1
super().__init__()

它工作不带参数通过移动堆栈帧中,第一个参数方法(通常为一个实例方法selfcls一个类方法,但可能有其他名称),发现的类(例如Child)的自由变量的名称(抬头__class__是一个免费的闭包变量的方法)。

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

间接兼容正向

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

对于经验丰富的开发人员来说,向前兼容性非常重要。您希望您的代码在更改时保持最小的更改。当你查看你的复习历史时,你想准确地看到什么在什么时候发生了变化。

您可以从单个继承开始,但是如果您决定添加另一个基类,您只需要使用基来更改行—如果您继承的类中的基发生更改(例如添加了mixin),那么您不会在这个类中更改任何内容。特别是在python2中,将参数设置为super和正确的方法参数是很困难的。如果您知道在单继承中正确地使用了super,那么调试就不会那么困难。

依赖注入

其他人可以使用你的代码并将父类注入方法解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
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之间注入一个类(为了测试或者其他原因):

1
2
3
4
5
6
7
8
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

使用非超子元素不能注入依赖项,因为你使用的子元素已经硬编码了方法,要在它自己的方法之后调用:

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

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

1
2
3
4
>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called

解决评论

Why in the world would this be useful?

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

我们希望方法按这个顺序查找。

对于在父类中定义的方法,如果要按照没有super的顺序查找下一个方法,则必须这样做

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

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

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

这两个子类都打算使用MRO中随后出现的相同名称来调用方法,这可能是在创建时没有意识到的另一个类。

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

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

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

结论

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

您想要引用的是下一个in-line的父类,而不是您看到的子类继承自的父类。

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


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

1
2
3
4
5
6
7
8
class A:
    def __init__(self):
        print("A.__init__()")

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

在python2中不能工作。class A必须是新型的,我。艾凡:class A(object)


我用super()玩了一会儿,认识到我们可以改变调用顺序。

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

1
2
3
4
5
    A
   / \
  B   C
   \ /
    D

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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()时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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元组的顺序。

1
2
3
4
5
    A
   / ?
  B ? C
   ? /
    D

对于补充阅读,我推荐以下答案:

C3超线性化例子(大层次结构)新旧样式类之间的重要行为更改关于新型类的内部故事


当调用super()解决父母的版本classmethod,实例方法,或staticmethod,我们想通过当前类的范围我们是作为第一个参数,表明父母的范围我们试图解决,作为第二个参数对象显示感兴趣的对象我们试图应用范围。

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

1
2
3
4
5
6
7
8
9
10
11
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()

1
2
3
4
5
6
7
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__()时,类的引用必须显式地作为第一个参数传递:

1
2
3
4
5
6
7
8
9
10
# 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)时传递的类引用。在大多数情况下,它恰好也是对子类的引用。在某些情况下可能不是这样,例如在多代继承的情况下。

1
super(A, cls)

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

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

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

1
2
3
4
5
6
7
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()

1
2
3
4
5
6
7
class A(object):
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

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

解释:

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

1
2
3
4
5
6
7
8
9
10
11
12
# 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()时,我们将子类作为第一个参数传递,将感兴趣的对象作为第二个参数传递,后者通常是对子类实例的引用。

1
super(A, self)

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

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

1
2
3
4
5
6
7
class A(object):
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

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

使用super和一个类方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
2
3
4
# 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()在classmethod解决其父母的版本,我们想通过当前子类作为第一个参数来表示我们试图解决父母的范围,和感兴趣的对象作为第二个参数来表示我们想应用范围的对象,一般是指子类本身或它的一个子类。

1
super(B, cls_or_subcls)

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

1
super(B, cls).alternate_constructor()

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

1
2
3
4
5
6
7
8
9
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是它的第二个参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
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)

1
2
3
class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

这很容易理解。

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

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

创建子实例时,其MRO(方法解析顺序)的顺序是基于继承的(子、SomeBaseClass、对象)。(假设某个基类除了默认对象之外没有其他父类)

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

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