use*_*785 522 python oop inheritance super
有什么区别:
class Child(SomeBaseClass):
def __init__(self):
super(Child, self).__init__()
Run Code Online (Sandbox Code Playgroud)
和:
class Child(SomeBaseClass):
def __init__(self):
SomeBaseClass.__init__(self)
Run Code Online (Sandbox Code Playgroud)
我已经看到super
在只有单继承的类中使用了很多.我可以看到为什么你在多重继承中使用它,但不清楚在这种情况下使用它的优点是什么.
Joh*_*kin 296
super()
单继承的好处是最小的 - 大多数情况下,您不必将基类的名称硬编码到使用其父方法的每个方法中.
但是,没有多重继承几乎是不可能的super()
.这包括常见的习惯用语,如mixins,接口,抽象类等.这扩展到后来扩展你的代码.如果有人后来想写一个扩展Child
和混合的类,他们的代码将无法正常工作.
Aar*_*all 287
SomeBaseClass.__init__(self)
Run Code Online (Sandbox Code Playgroud)
意味着调用SomeBaseClass
的__init__
.而
super(Child, self).__init__()
Run Code Online (Sandbox Code Playgroud)
表示__init__
从Child
实例的方法解析顺序(MRO)中的父类调用绑定.
如果实例是Child的子类,则MRO中可能会有另一个父级.
编写类时,您希望其他类能够使用它.super()
使其他类更容易使用您正在编写的类.
正如Bob Martin所说,一个好的架构可以让你尽可能地推迟决策.
super()
可以启用这种架构.
当另一个类为您编写的类创建子类时,它也可以从其他类继承.并且这些类可以__init__
在此之后__init__
基于方法解析的类的顺序来实现.
没有super
你可能会硬编码你正在编写的类的父级(如示例所示).这意味着您不会__init__
在MRO中调用下一个,因此您无法重用其中的代码.
如果您正在编写自己的代码供个人使用,您可能不关心这种区别.但是,如果您希望其他人使用您的代码,那么使用代码super
的用户可以获得更大的灵活性.
这适用于Python 2和3:
super(Child, self).__init__()
Run Code Online (Sandbox Code Playgroud)
这仅适用于Python 3:
super().__init__()
Run Code Online (Sandbox Code Playgroud)
通过在堆栈框架中向上移动并获取方法的第一个参数(通常self
用于实例方法或cls
类方法 - 但可以是其他名称)并Child
在自由变量中查找类(例如),它不使用任何参数(它被查找为名称__class__
作为方法中的自由闭包变量.
我更喜欢演示交叉兼容的使用方式super
,但如果你只使用Python 3,你可以不带参数调用它.
它给你带来了什么?对于单继承,从静态分析的角度来看,问题中的示例实际上是相同的.但是,using 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__()
Run Code Online (Sandbox Code Playgroud)
假设您在对象中添加了另一个类,并希望在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
Run Code Online (Sandbox Code Playgroud)
使用un-super子节点无法注入依赖项,因为您正在使用的子节点已对其自身调用的方法进行了硬编码:
>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called
Run Code Online (Sandbox Code Playgroud)
但是,使用子级的类super
可以正确地注入依赖项:
>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called
Run Code Online (Sandbox Code Playgroud)
为什么这个世界会有用呢?
Python通过C3线性化算法线性化复杂的继承树,以创建方法解析顺序(MRO).
我们希望按顺序查找方法.
对于在父级中定义的方法,在没有的情况下找到该顺序中的下一个方法super
,就必须这样做
该
UnsuperChild
不该访问InjectMe
.为什么不是"总是避免使用super
" 的结论?我在这里错过了什么?
该UnsuperChild
不会不访问InjectMe
.它可以UnsuperInjector
访问InjectMe
- 但不能从它继承的方法中调用该类的方法UnsuperChild
.
两个Child类都打算使用MRO中下一个相同的名称来调用一个方法,这可能是它在创建时不知道的另一个类.
没有super
硬编码其父方法的那个 - 因此限制了其方法的行为,并且子类不能在调用链中注入功能.
在一个与 super
具有更大的灵活性.可以拦截方法的调用链并注入功能.
您可能不需要该功能,但您的代码的子类可能.
始终用于super
引用父类而不是硬编码.
您打算引用的是下一行的父类,而不是您看到该子类继承的父类.
不使用super
可以对代码的用户施加不必要的约束.
mha*_*wke 35
是不是所有这些都假设基类是一个新式的类?
class A:
def __init__(self):
print("A.__init__()")
class B(A):
def __init__(self):
print("B.__init__()")
super(B, self).__init__()
Run Code Online (Sandbox Code Playgroud)
在Python 2中不起作用.class A
必须是新式的,即:class A(object)
Inf*_*ion 30
我玩了一下super()
,并且认识到我们可以改变通话顺序.
例如,我们有下一个层次结构:
A
/ \
B C
\ /
D
Run Code Online (Sandbox Code Playgroud)
在这种情况下,D的MRO将是(仅适用于Python 3):
In [26]: D.__mro__
Out[26]: (__main__.D, __main__.B, __main__.C, __main__.A, object)
Run Code Online (Sandbox Code Playgroud)
让我们创建一个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
Run Code Online (Sandbox Code Playgroud)
因此我们可以看到解决方案顺序与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
Run Code Online (Sandbox Code Playgroud)
我们有一个不同的顺序,它颠倒了MRO元组的顺序.
A
/ ?
B ? C
? /
D
Run Code Online (Sandbox Code Playgroud)
如需更多阅读,我会建议下一个答案:
Mic*_*oka 18
当调用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
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
说明:
尽管通常__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)
Run Code Online (Sandbox Code Playgroud)
2-当调用super()
get to parent class时,我们传递子类A
作为它的第一个参数,然后我们传递一个对感兴趣的对象的引用,在这种情况下它是A.__new__(cls)
被调用时传递的类引用.在大多数情况下,它恰好也是对子类的引用.在某些情况下,它可能不是,例如在多代继承的情况下.
super(A, cls)
Run Code Online (Sandbox Code Playgroud)
3-因为一般规则__new__()
是静态方法,所以super(A, cls).__new__
也会返回一个静态方法,并且需要明确地提供所有参数,包括在这种情况下对insterest对象的引用cls
.
super(A, cls).__new__(cls, *a, **kw)
Run Code Online (Sandbox Code Playgroud)
4-没有做同样的事情 super
class A(object):
def __new__(cls, *a, **kw):
# ...
# whatever you want to specialize or override here
# ...
return object.__new__(cls, *a, **kw)
Run Code Online (Sandbox Code Playgroud)
super
实例方法例如super()
从内部使用__init__()
class A(object):
def __init__(self, *a, **kw):
# ...
# you make some changes here
# ...
super(A, self).__init__(*a, **kw)
Run Code Online (Sandbox Code Playgroud)
说明:
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)
Run Code Online (Sandbox Code Playgroud)
当调用2- super()
内__init__()
我们通过子类作为第一个参数和感兴趣作为第二个参数,这在一般是子类的实例的引用的对象.
super(A, self)
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
super
classmethodclass 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)
Run Code Online (Sandbox Code Playgroud)
说明:
1-可以直接从类中调用classmethod,并将第一个参数作为类的引用.
# calling directly from the class is fine,
# a reference to the class is passed implicitly
a = A.alternate_constructor()
b = B.alternate_constructor()
Run Code Online (Sandbox Code Playgroud)
2-当super()
在类方法中调用以解析其父类的版本时,我们希望将当前子类作为第一个参数传递,以指示我们尝试解析哪个父级的范围,并将感兴趣的对象作为第二个参数指示我们要将该范围应用于哪个对象,通常是对子类本身或其子类之一的引用.
super(B, cls_or_subcls)
Run Code Online (Sandbox Code Playgroud)
3-呼叫super(B, cls)
解决了范围A
并将其应用于cls
.由于alternate_constructor()
是一个类方法,调用super(B, cls).alternate_constructor(...)
将隐式地将引用cls
作为第一个参数传递给A
的版本alternate_constructor()
super(B, cls).alternate_constructor()
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
上述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)
Run Code Online (Sandbox Code Playgroud)
Ray*_*ger 12
这个小例子涵盖了所有有趣的案例:
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')
Run Code Online (Sandbox Code Playgroud)
调用的确切顺序由调用方法的实例确定:
>>> a = A()
>>> b = B()
>>> c = C()
>>> d = D()
Run Code Online (Sandbox Code Playgroud)
例如a,没有超级调用:
>>> a.m()
A
Run Code Online (Sandbox Code Playgroud)
例如b,祖先链是B -> A -> object
:
>>> type(b).__mro__
(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
>>> b.m()
B start
A
B end
Run Code Online (Sandbox Code Playgroud)
例如c,祖先链是C -> A -> object
:
>>> type(c).__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
>>> b.m()
C start
A
C end
Run Code Online (Sandbox Code Playgroud)
例如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
Run Code Online (Sandbox Code Playgroud)
回答了“super 在 Python 中做了什么?”的问题,接下来的问题是如何有效地使用它。请参阅此分步教程或此45 分钟视频。
许多很好的答案,但对于视觉学习者:首先让我们探索 super 的参数,然后没有。
想象一下,有一个jack
从类创建的实例,该类Jack
具有如图中绿色所示的继承链。调用:
super(Jack, jack).method(...)
将使用jack
(其继承树按特定顺序)的 MRO(方法解析顺序),并将从Jack
. 为什么可以提供父类?好吧,如果我们从实例开始搜索jack
,它会找到实例方法,重点是找到它的父方法。
如果不向 super 提供参数,则就像传入的第一个参数是 的类self
,而传入的第二个参数是self
。这些是在 Python3 中为您自动计算的。
但是,如果我们不想使用Jack
的方法,而不是传入Jack
,我们可以传入Jen
以开始向上搜索 from 的方法Jen
。
它在一个时间(宽度不深度),例如,如果搜索一个层Adam
和Sue
两者都具有所要求的方法,从所述一个Sue
将首先找到。
如果Cain
和Sue
都具有所需的方法,则Cain
首先调用 的方法。这在代码中对应于:
Class Jen(Cain, Sue):
Run Code Online (Sandbox Code Playgroud)
MRO 从左到右。
归档时间: |
|
查看次数: |
209679 次 |
最近记录: |