问题:“ super”在Python中做什么?
之间有什么区别:
class Child(SomeBaseClass):
def __init__(self):
super(Child, self).__init__()
和:
class Child(SomeBaseClass):
def __init__(self):
SomeBaseClass.__init__(self)
我看到super
在只有单一继承的类中经常使用它。我知道为什么您会在多重继承中使用它,但不清楚在这种情况下使用它的好处。
回答 0
super()
单一继承的好处很小-大多数情况下,您不必将基类的名称硬编码到使用其父方法的每个方法中。
但是,如果不使用,几乎不可能使用多重继承super()
。这包括常见的惯用语,例如mixin,接口,抽象类等。这扩展到了以后扩展您的代码的代码。如果以后有人要编写扩展的类Child
和mixin,则他们的代码将无法正常工作。
回答 1
有什么不同?
SomeBaseClass.__init__(self)
表示呼叫SomeBaseClass
的方式__init__
。而
super(Child, self).__init__()
表示__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__()
这仅适用于Python 3:
super().__init__()
它不带任何参数,方法是在堆栈框架中上移并获取方法的第一个参数(通常self
用于实例方法或cls
类方法-但可以是其他名称),然后Child
在自由变量中找到类(例如)(__class__
在方法中将其作为自由闭合变量的名称进行查找)。
我更喜欢演示使用的交叉兼容方式super
,但是如果您仅使用Python 3,则可以不带任何参数调用它。
具有前向兼容性的间接
它给你什么?对于单继承,从静态分析的角度来看,问题的示例实际上是相同的。但是,使用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__()
假设您向对象添加了另一个类,并想在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线性化算法将复杂的继承树线性化,以创建方法解析顺序(MRO)。
我们希望按该顺序查找方法。
对于在父级中定义的方法,如果不按顺序查找下一个方法super
,则必须
- 从实例的类型获取mro
- 寻找定义方法的类型
- 用该方法找到下一个类型
- 绑定该方法并使用所需的参数调用它
该
UnsuperChild
不该访问InjectMe
。为什么没有“总是避免使用super
” 的结论?我在这里想念什么?
该UnsuperChild
不会不访问InjectMe
。可以UnsuperInjector
访问InjectMe
-却无法从其继承的方法调用该类的方法UnsuperChild
。
两个Child类都打算使用MRO中紧随其后的相同名称来调用方法,这可能是它在创建时不知道的另一个类。
没有super
硬编码其父方法的方法-因此限制了其方法的行为,并且子类无法在调用链中注入功能。
在一个与 super
具有更大的灵活性。这些方法的调用链可以被拦截并注入功能。
您可能不需要该功能,但是代码的子类却可能需要。
结论
始终使用super
引用父类而不是对其进行硬编码。
您打算引用的是下一行的父类,而不是您看到子级继承的父类。
不使用super
会给您的代码用户带来不必要的限制。
回答 2
我与玩了一点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
如需其他阅读,我建议下一个答案:
回答 3
难道不是所有这些都假设基类是新型类吗?
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)
回答 4
当调用super()
解析为父方法的类方法,实例方法或静态方法时,我们希望将其所在范围的当前类作为第一个参数传递,以指示我们要解析为哪个父方法的范围,并作为第二个参数是感兴趣的对象,用于指示我们要将该范围应用于哪个对象。
考虑一个类层次结构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
与静态方法一起使用
例如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
作为其第一个参数传递,然后传递对感兴趣对象的引用,在这种情况下,它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
与实例方法
例如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
作为第一个参数传递给父级的__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()
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)
回答 5
有很多不错的答案,但是对于视觉学习者来说:首先让我们以super为参数进行探索,然后再以super为参数。
想象有一个jack
从类创建的实例,该实例Jack
具有继承链,如图中的绿色所示。调用:
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是从左到右。
回答 6
这里有一些很好的答案,但是super()
在层次结构中的不同类具有不同签名的情况下,它们并没有解决如何使用的问题……尤其是在__init__
为了回答这一部分并能够有效地使用,super()
我建议阅读我的答案super()并更改合作方法的签名。
这只是这种情况的解决方案:
- 层次结构中的顶级类必须继承自定义类,例如
SuperObject
:- 如果类可以采用不同的参数,则始终将您收到的所有参数作为关键字参数传递给超函数,并始终接受
**kwargs
。
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()
用法示例:
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
回答 7
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, self
, super
在self
实例的MRO中搜索,然后返回Child的下一个代理对象(在本例中为SomeBaseClass),然后此对象调用__init__
SomeBaseClass 的方法。换句话说,如果是super(SomeBaseClass,self)
,则super
返回的代理对象将是object
对于多继承,MRO可以包含许多类,因此基本上super
可以让您决定要在MRO中开始搜索的位置。
回答 8
考虑以下代码:
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
和P
或者Q
甚至可以从当你写你不知道另一个文件参与X
和Y
。因此,基本上,即使Y的签名与一样简单,您也不知道super(Child, self)
在编写时将引用什么内容。这就是为什么超级可能是更好的选择。class Y(X)
Y(X)