以下内容引用自官方手册
返回一个代理对象,其可以用委派的方法调用类型的父系或兄弟类,这对于访问继承的方法(已经被重写的方法)很有用,检索顺序与使用getattr()相同。
类型的__mro__属性列出了getattr()和super()所用的方法解析检索顺序,该属性是动态的,并且无论何时更新继承层次都会改变。
如果第二个参数缺省,super对象的返回是未被绑定的;如果第二个参数是一个对象,则isinstance(obj, type)必须是True;如果第二个参数是一个类型(type),则issubclass(type2, type)必须是True(这对于类方法很有用)。
super有两种常规使用情形:
对于两种使用情况,常规的superclass调用可以像这样:
class C(B):
def method(self, arg):
super().method(arg) # 这与下面的相同:
# super(C, self).method(arg)
注意:对于显式加点属性查找,super()可以作为绑定进程的一部分执行,例如super().__getitem__(name),为搜索类(支持协同多重继承)在可预测顺序内实现它自己的__getattribute__()方法。因此,对于使用声明或运算符(例如super()[name])的显式查找,super()是未定义的。
同样需要注意,除了0参数形式之外,super()不限制使用内部方法。两个参数形式明确指定了参数,且做出了合适的引用。0参数形式只能在类定义内部工作,其作为编译器而填入了必要的细节,确保可以正确的检索定义的类,同样可以也为普通方法访问当前实例。
以下内容来自官方手册推荐的一篇博文 — 《Python’s super() considered super!》,部分Python 2的内容未摘录。
如果你没被Python内建的super()惊艳到,有可能是你还没有真正了解它的能力,又或者你还不知道如何有效的使用它。
已经写了关于super()的文章,但大多数都失败了,而这篇文章则试图通过以下几点改善这种状况:
使用Python 3语法,让我们从基础使用案例开始,为一个内建类的子类扩展方法:
class LoggingDict(dict):
def __setitem__(self, key, value):
logging.info('Settingto %r' % (key, value))
super().__setitem__(key, value)
这个类拥有它的父类(dict)同样的能力,但是它扩展了__setitem__方法,使得无论何时更新key都记录日志,在记录日志后,方法使用super()对实际上用key/value对更新字典的工作进行委派。
在引入super()前,我们可以用dict.setitem(self, key, value)硬链接调用。无论如何,super()更好,因为它是一个计算的间接引用。
间接引用的好处之一是我们不必通过名字指定委派类,如果你编辑资源代码将基础类切换为其他映射,那么super()引用将自动遵循,代码如下:
class LoggingDict(SomeOtherMapping): # 新的基础类
def __setitem__(self, key, value):
logging.info('Settingto %r' % (key, value))
super().__setitem__(key, value) # 不需要修改
除了隔离修改外,还有其他主要益处,其中一个可能对来自静态语言的人不太熟悉,虽然间接引用在运行时计算,但是我们有影响计算的自由,这样间接引用就可以指向其他类。
计算依赖于调用super的类和祖先类的实例树。第一个组成部分,调用super的类由这个类的资源代码决定,在我们的案例中,super()是在LoggingDict.__setitem__方法中调用的,这部分是固定的;第二个或更多有趣的组成部分是变量(我们可以用丰富的祖先树来创建新的子类)。
让我们使用这点来继续创建一个有序的日志字典,而不用修改我们现有的类:
class LoggingOD(LoggingDict, collections.OrderedDict):
pass
我们新建类的祖先树有:LoggingOD、LoggingDict、OrderedDict、dict、object。为达到我们的目的,重点是OrderedDict要在LoggingDict之后、dict之前插入。这意味着在LoggingDict.__setitem__中super的调用,现在替代dict,将key/value更新的工作派遣至OrderedDict。
关于这点需要思考一会,这里我们不对LoggingDict修改资源代码,替代的我们创建了一个子类,它唯一的逻辑就是组合两个已存在的类,并控制它们的搜索顺序。
我们上面调用的搜索顺序或祖先树在官方上认知为方法解决顺序或*MRO,可以简单的通过打印__mro__属性来看到它:
>>> pprint(LoggingOD.__mro__)
(<class '__main__.LoggingOD'>,
<class '__main__.LoggingDict'>,
<class 'collections.OrderedDict'>,
<class 'dict'>,
<class 'object'>)
Python 3 的方法为:
>>> for i in LoggingOD.__mro__:
print(i)
<class '__main__.LoggingOD'>
<class '__main__.LoggingDict'>
<class 'collections.OrderedDict'>
<class 'dict'>
<class 'object'>
如果我们的目标是用MRO创建一个子类,那么我们需要知道它是如何被计算的。基础是很简单的,上面的序列里包含了类、它的基类和这些类的基类等等,一直到object(它是所有类的根类)。序列是有序的,即一个类总是在它的父类前出现,并且如果有多重父类,它们以基类元组保持着相同的顺序。
上面展示的MRO遵从以下的顺序约束:
解决这些约束问题的做法被称为线性化(linearization),关于这个主题有很多好的文章,但用MRO创建子类我们想要的,我们只需要知道两条约束:子类在它们的父类前;遵从__bases__中的出场顺序。
super()的职责是将方法调用委派到实例的祖先树中的一些类,为处理重新排序方法的调用,类需要设计的有协同性,这就产生了三个实践问题:
1)让我们先看看获取调用者参数匹配被调用方法标识的策略,这比传统的方法调用(即预先知道被调用者)稍具挑战性 — 使用super(),在写类时不知道被调用者(因为后写的子类可能将新类引入MRO)。
一种方法是使用位置参数的固定标识进行粘接,这种情况下用像__setitem__方法效果很好,它有两个参数的固定标识(key和value),这种技术在LoggingDict案例中有所体现,其中__setitem__与LoggingDict、dict有相同的标识。
另一种灵活的方式是拥有祖先树中的所有方法,协同设计为:可接受关键字参数和关键字参数字典;可移除任意参数;可使用**kwds提取剩余参数,为链中最后的调用交托空字典。
根据需要每层剥离关键字参数,以便最后的空字典可以传递给不需要参数的方法(例如:object.__init__不需要参数)。
class Shape:
def __init__(self, shapename, **kwds):
self.shapename = shapename
super().__init__(**kwds)
class ColoredShape(Shape):
def __init__(self, color, **kwds):
self.color = color
super().__init__(**kwds)
cs = ColoredShape(color='red', shapename='circle')
2)已经看了获取调用者/被调用者参数模式的匹配策略,现在让我们看看如何确保目标方法存在。
上面的案例展示了最简单的情况,我们知道object有__init__方法,并且这个对象在MRO链中总是最新的类,因此任意super().__init__调用的序列都肯定以object.__init__方法结尾。换句话说,我们可以肯定super()调用的目标是保证一定存在,并且不会引发AttributeError异常。
对于object没有的方法的情形(例如draw()),我们需要写一个根类来确保在对象前调用,根类的职责是只是获取方法调用,而不是使用super()进一步产生一个调用。
Root.draw也可以采用防御式编程,使用assertion来确保它不会遮盖链中其他新的draw()方法。这种情况是可能发生的,如果子类错误的结合了某个含有draw()方法的类,但其未从Root.继承。代码如下:
class Root:
def draw(self):
# 确保链在这里终止
assert not hasattr(super(), 'draw')
class Shape(Root):
def __init__(self, shapename, **kwds):
self.shapename = shapename
super().__init__(**kwds)
def draw(self):
print('Drawing. Setting shape to:', self.shapename)
super().draw()
class ColoredShape(Shape):
def __init__(self, color, **kwds):
self.color = color
super().__init__(**kwds)
def draw(self):
print('Drawing. Setting color to:', self.color)
super().draw()
cs = ColoredShape(color='blue', shapename='square')
cs.draw()
如果子类想将其他类注入到MRO,这些类也需要继承自Root,这样就没有途径可以使调用draw()能到达object,而不用被Root.draw终止。这点应该清楚的记录下来,以便人们在写新的协同类时能明确来自Root的子类。这个限制与Python自身的要求区别不大,即所有新的异常必须继承自BaseException。
3)上面展示的技术需要确保super()调用的某个方法已知存在,且标识正确;无论如何,我们仍然依赖在每一步都调用super(),以便委派的链在进程中不被打断。如果我们设计的类具有协同性(在链中的每个方法添加super()),这个目的也是容易实现的。
上面列出的三个技术意味着,协同类的设计可以通过子类构成或重排序。
有时候,某个子类可能想用一个第三方类使用协同多重继承技术,然而它并不是为此设计的(也许它引用的方法并未使用super(),或者并未继承自根类)。这种情况下,可以通过创建一个适配器类来弥补。
例如:下面的Moveable类并未使用super()调用,它有一个__init__()标识,与object.__init__并不匹配,而且它没有继承自Root:
class Moveable:
def __init__(self, x, y):
self.x = x
self.y = y
def draw(self):
print('Drawing at position:', self.x, self.y)
如果想让它用于我们协同设计的ColoredShape层,我们需要用必要的super()调用来创建一个适配器:
class MoveableAdapter(Root):
def __init__(self, x, y, **kwds):
self.movable = Moveable(x, y)
super().__init__(**kwds)
def draw(self):
self.movable.draw()
super().draw()
class MovableColoredShape(ColoredShape, MoveableAdapter):
pass
MovableColoredShape(color='red', shapename='triangle',
x=10, y=20).draw()
在Python 2.7和3.2中,collections模块都有Counter和OrderedDict类,很容易组合这些类来创建OrderedCounter:
from collections import Counter, OrderedDict
class OrderedCounter(Counter, OrderedDict):
'Counter that remembers the order elements are first seen'
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__,
OrderedDict(self))
def __reduce__(self):
return self.__class__, (OrderedDict(self),)
oc = OrderedCounter('abracadabra')
当子类化一个内建类型(例如dict()),经常需要重写或扩展多重方法,上面的示例中,__setitem__的扩展不被例如dict.update等所使用,所以也需要对这些进行扩展。这个要求不是super()仅有的,它在任何内建类型子类化时都会出现。
如果某个类依赖某个在其他类之前的父类(例如:LoggingOD依赖 — LoggingDict在OrderedDict之前,OrderedDict在dict之前),那么添加断言来验证并记录预计的方法解决顺序:
position = LoggingOD.__mro__.index
assert position(LoggingDict) < position(OrderedDict)
assert position(OrderedDict) < position(dict)
getattr()
__mro__
isinstance(obj, type)
issubclass(type2, type)
钻石图
Python MRO文档
C3线性
适配器