您的位置:首页 > 移动开发 > Objective-C

Python 内建函数 - super([type[, object-or-type]])

2017-03-24 15:15 531 查看
函数参考

实践使用指南
基础

搜索顺序

实践建议

如何包含一个非协同类

完整代码

注意和参考

拓展阅读

函数参考

以下内容引用自官方手册

返回一个代理对象,其可以用委派的方法调用类型的父系或兄弟类,这对于访问继承的方法(已经被重写的方法)很有用,检索顺序与使用getattr()相同。

类型的__mro__属性列出了getattr()和super()所用的方法解析检索顺序,该属性是动态的,并且无论何时更新继承层次都会改变。

如果第二个参数缺省,super对象的返回是未被绑定的;如果第二个参数是一个对象,则isinstance(obj, type)必须是True;如果第二个参数是一个类型(type),则issubclass(type2, type)必须是True(这对于类方法很有用)。

super有两种常规使用情形:

在类层次中的单个继承,super可用于引用父类,而不用明确命名它们,因此使得代码更具可维护性。这种使用与其他编程语言中的super相较很接近。

第二种使用情形,支持在动态执行环境中协同多重继承,这种用法是Python独创的,在静态编译语言或只支持单继承语言中还没见过。在那些需要用多重基类实现相同方法地方,super方法使得实现“钻石图”(“diamond diagrams”)成为可能。一个好的设计规范要求这种方法在各种情况下具有相同的调用标识,因为运行时决定调用的顺序,而调用顺序要适应类层次的修改,且调用顺序可以包含运行时之前未知的同级类。

对于两种使用情况,常规的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()的文章,但大多数都失败了,而这篇文章则试图通过以下几点改善这种状况:

提供实际使用案例

给出一个清楚的构思模型来阐明它是如何工作的

展示每次让可以它投入运行的谍报

使用super()创建类的具体建议

有帮助的真实举例:抽象ABCD钻石图

基础

使用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创建一个子类,那么我们需要知道它是如何被计算的。基础是很简单的,上面的序列里包含了类、它的基类和这些
ce3d
类的基类等等,一直到object(它是所有类的根类)。序列是有序的,即一个类总是在它的父类前出现,并且如果有多重父类,它们以基类元组保持着相同的顺序。

上面展示的MRO遵从以下的顺序约束:

LoggingOD在它的父类之前,LoggingDict和OrderedDict

LoggingDict在OrderedDict之前,因为LoggingOD.__bases__是(LoggingDict, OrderedDict)

LoggingDict在它的父类(dict)之前

OrderedDict在它的父类(dict)之前

dict在它的父类(object)之前

解决这些约束问题的做法被称为线性化(linearization),关于这个主题有很多好的文章,但用MRO创建子类我们想要的,我们只需要知道两条约束:子类在它们的父类前;遵从__bases__中的出场顺序。

实践建议

super()的职责是将方法调用委派到实例的祖先树中的一些类,为处理重新排序方法的调用,类需要设计的有协同性,这就产生了三个实践问题:

super()调用的方法需要存在

调用者和被调用者需要有一致的参数标识

方法的每次出现都需要使用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)


对于好的线性算法文章可以查看Python MRO文档和Wikipedia网站查找C3线性

拓展阅读

getattr()

__mro__

isinstance(obj, type)

issubclass(type2, type)

钻石图

Python MRO文档

C3线性

适配器
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  python
相关文章推荐