Python’s super() considered super!
如果你没有被Python的super()惊愕过,那么要么是你不了解它的威力,要么就是你不知道如何高效地使用它。
有许多介绍super()的文章,这一篇与其它文章的不同之处在于:
- 提供了实例
- 阐述了它的工作模型
- 展示了任何场景都能使用它的手段
- 有关使用super()的类的具体建议
- 基于抽象ABCD钻石模型的实例
下面是一个使用Python 3语法,扩展了builtin类型dict
中方法的子类:
import pprint
import logging
import collections
class LoggingDict(dict):
def __setitem__(self, key, value):
logging.info('Setting %r to %r' % (key, value))
super().__setitem__(key, value)
LoggingDict
继承了父类dict的所有特性,同时其扩展了__setitem__
方法来记录被设置的key;在记录日志之后,该方法用super()
将真正的更新操作代理给其父类。
我们可以使用dict.__setitem__(self, key, value)
来完成super()
的功能,但是super()
更优,因为它是一个计算出来的间接引用。
间接
的一个好处是,我们不需要使用名字来指定代理类。如果你将基类换成其它映射(mapping)类型,super()
引用将会自动调整。你只需要一份代码:
class LoggingDict(someOtherMapping): # 新的基类
def __setitem__(self, key, value):
logging.info('Setting %r to %r' % (key, value))
super().__setitem__(key, value) # 无需改变
对于计算出的间接引用,其除了隔离变化外,依赖于Python的动态性,可以在运行时改变其指向的class。
计算取决于类在何处被调用以及实例的继承树;super在何处调用取决于类的源码,在上例中super()
是在LoggingDict.__setitem__
方法中被调用的;实例的继承树在后文详述。
下面先构造一个有序的logging字典:
class LoggingOD(LoggingDict, collections.OrderedDict):
pass
新class的继承树是:LoggingOD
, LoggingDict
, OrderedDict
, dict
, object
。出人意料的是OrderedDict
竟然介于LoggingDict
之后和dict
之前,这意味着LoggingDict.__setitem__
中super()
调用会将键/值的更新委托给OrderedDict
而不是dict
。
在上例中,我们并没有修改LoggingDict
的源码,只是创建了一个子类,这个子类的唯一逻辑是组合两个已有的类并控制它们的搜索顺序(search order)。
Search Order
上面提到的搜索顺序
或者继承树
的官方称谓是Method Resolution Order
(方法解析顺序)即MRO
。可以用__mro__
属性方便地打印出对象的MRO:
pprint.pprint(LoggingOD.__mro__)
(<class '__main__.LoggingOD'>,
<class '__main__.LoggingDict'>,
<class 'collections.OrderedDict'>,
<class 'dict'>,
<class 'object'>)
如果我们想创建出其MRO符合我们意愿的子类,就必须知道它是如何计算的。MRO的计算很简单,MRO序列包含类、类的基类以及基类们的基类......这个过程持续到到达object
,object
是所有类的根类;这个序列中,子类总是出现在其父类之前,如果一个子类有多个父类,父类按照子类定义中的基类元组的顺序排列。
上例中MRO是按照这些约束计算出来的:
- LoggingOD在其父类LoggingDict, OrderedDict之前
- LoggingDict在OrderedDict之前是因为LoggingOD.bases是(LoggingDict, OrderedDict)
- LoggingDict在它的父类dict之前
- OrderedDict在它的父类dict之前
- dict在它的父类object之前
解析这些约束的过程称为线性化(linearization)。创建出MRO符合我们期望的子类只需知道两个约束:子类在父类之前;符合__bases__
里的顺序。
Practical Advice
super()用来将方法调用委托给其继承树中的一些类。为了让super能正常作用,类需要协同设计。下面是三条简单的解决实践:
- 被调用的super()需存在
- 调用者和被调用者的参数签名需匹配
- 方法的任何出现都需要使用super()
1) 先来看一下使调用者和被调者参数签名匹配的策略。对于一般的方法调用而言,被调者在被调之前其信息是已经获知的;然而对于super(),直到运行时才能确定被调者(因为后面定义的子类可能会在MRO中引入新的类)。
一种方法是使用positional参数固定签名。这种方法对于像__setitem__
这种只有两个参数的固定签名是适用的。LoggingDict
例子中__setitem__
的签名和dict
中一致。
另一种更灵活的方法是规约继承树中的每一个方法都被设计成接受keyword参数和一个keyword参数字典,“截留住”自身需要的参数,然后将剩下的参数使用**kwds
转发至父类中的方法,使得调用链中的最后一次调用中参数字典为空(即沿着继承树一层一层地将参数剥离,每层都留下自己需要的,将余下的参数传递给基类)。
每一层都会剥离其所需的参数,这样就能保证最终将空字典传递给不需要参数的方法(比如,object.__init__
不需要参数):
class Shape:
def __init__(self, shapename, **kwds):
self.shapename = shapename
super().__init__(**kwds)
class ColoredShape(Shape):
def __init__(self, color, **kwargs):
self.color = color
super().__init__(**kwargs)
cs = ColoredShape(color='red', shapename='circle')
2) 现在来看一下如何保证目标方法存在。
上例仅展示了最简单的情形。我们知道object有一个__init__
方法,它也总是MRO链中最后一个类,因此任意数量的super().__init__
最终都会以调用object.__init__
结束。换言之,我们可以保证调用继承树上任意对象的super().__init__
方法都不会以产生AttributeError而失败。
对于object没有的方法(比如draw()方法),我们需要写一个根类并保证它在object对象之前被调用。根类的作用仅仅是将方法调用“截住”而不会再进一步调用super()。
Root.draw也可以使用defensive programming策略使用断言来保证draw()方法不会再被调用。
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()
Drawing. Setting color to: blue
Drawing. Setting shape to: square
如果子类想在MRO中注入其它类,那么这些类也需要继承自Root,这样在继承路径上的任何类调用draw()方法都不会最终代理至object而抛出AttributeError。这一规定需在文档中明确,这样别人在写新类的时候才知道需要继承Root。这一约束与Python中要求所有异常必须继承自BaseException并无不同。
3) 上面讨论的两点保证了方法的存在以及签名的正确,然而我们还必须保证在代理链上的每一步中都super()都被调用。这一目标很容易达成,只需要协同设计每一个相关类——在代理链上的每一步中增加一个supper()
How to Incorporate a Non-cooperative Class
在某些场景下,子类可能希望使用多重继承,其大部分父类都是协同设计的,同时也需要继承自一个第三方类(可能将要使用的方法没有使用super
或者该类没有继承自根类)。这种情形很容易通过使用adapter class来解决。
例如,下面的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层级一起使用的话,我们需要创建一个适配器(adapter),它调用了必须的super()方法。
class MoveableAdapter(Root):
def __init__(self, x, y, **kwds):
self.moveable = Moveable(x, y)
super().__init__(**kwds)
def draw(self):
self.moveable.draw()
super().draw()
class MoveableColoredShape(ColoredShape, MoveableAdapter):
pass
MoveableColoredShape(color='red', shapename='triangle', x=10, y=20).draw()
Drawing. Setting color to: red
Drawing. Setting shape to: triangle
Drawing at position: 10 20
Complete Example - Just for Fun
在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')
pprint.pprint(oc)
OrderedCounter(OrderedDict([('a', 5), ('b', 2), ('r', 2), ('c', 1), ('d', 1)]))
Notes and References
当子类化诸如dict()的builtin时,通常需要同时重载或者扩展多个方法。在上面的例子中,_setitem_扩展不能用于诸如dict.update等其它方法,因此可能需要扩展这些方法。这一需求不止限于super(),子类化builtins时都需要。
在多继承中,如果要求父类按照指定顺序(例如,LoggingOD需要LoggingDict在OrderedDict前面,而OrderedDict在dict前面),可以利用断言来验证或者使用文档来表明方法解析顺序:
position = LoggingOD.__mro__.index
assert position(LoggingDict) < position(collections.OrderedDict)
assert position(OrderedDict) < position(dict)
关于线性化算法可以参阅Python MRO documentation和Wikipedia entry for C3 Linearization。
Dylan编程语言)有一个像Python的super()的next-method方法,可以参见Dylan's class docs来了解它如何工作。
上面例子中使用的是Python 3的super(),Python 2的语法与之不同之处在于其
type
和object
参数须是显式的,另外Python 2中的super()只适用于新式类。
Python’s super() considered super!的更多相关文章
- Python面试题之Super函数
这是个高大上的函数,在python装13手册里面介绍过多使用可显得自己是高手 23333. 但其实他还是很重要的. 简单说, super函数是调用下一个父类(超类)并返回该父类实例的方法. 这里的下一 ...
- 巩固java(四)----super和super()
引言: 一个公司里有普通员工和经理,他们之间有很多共同点,但也有一些差异,比如薪水问题,普通员工只有普通工资,经理在完成绩效后有一定比例的奖金.这时我们可以定义两个类Employee和Manager, ...
- react super() and super(props)
subclass: subclass is a class that extends another class. 即子类. In ES2015, to use 'this' in subclasse ...
- Java学习——this、this()、super 和 super()的使用
编写程序:说明 this.super 和 super()的用法.程序首先定义 Point(点)类,然后创建点的子类 Line(线)),最后通过 LX7_3 类输出线段的长度. package Pack ...
- react中constructor和super()以及super(props)的区别。
react中这两个API出镜率超级高,但是一直不太懂这到底是干嘛的,有什么用:今天整理一下,方便自己查看同时方便大家. 1.constructor( )-----super( )的基本含义 const ...
- python 继承中的super
python继承中子类访问父类的方法(包括__init__)主要有两种方法,一种是调用父类的未绑定方法,另一种是使用super(仅仅对于新式类),看下面的两个例子: #coding:utf-8 cla ...
- python类中的super,原理如何?MRO是什么东东?
下面这个URL解释得比较清楚. http://python.jobbole.com/86787/?utm_source=group.jobbole.com&utm_medium=related ...
- python singleton design pattern super() 多继承
python singleton design pattern decorate baseclass metaclass import module super() 一.A decorator de ...
- 【python】class之super关键字的作用
在Python类的方法(method)中,要调用父类的某个方法,在Python 2.2以前,通常的写法如代码段1: 代码段1: class A: def __init__(self): prin ...
随机推荐
- Hive 的企业优化
优化 数据优化 一.从大表拆分成小表(更快地检索) 引用:Hive LanguageManual DDL eg2:常用于分表 create table if not exists default.ce ...
- [工具使用]xshell 中“快速命令集”的使用
突然看到朋友的xshell比我多一个按钮,且一点,哈哈哈 ,实现了很炫酷的功能,耐不住好奇,问了一句,原来是快速命令集! 1.选择快速命令集(两种方法a&b) a:文件 > 属性 > ...
- 二分查找iOS
二分查找(也称折半查找)是很常见的一种在数组中查找数据的算法,作为一名程序员是应该必须会的.它的基础思想:获取数组的中间值,将数组分割成两份,利用查找的值跟中间值进行比较,如果查找的值大于中间值,就在 ...
- Intellij 设置只更新静态文件(js、view、css)的方法
1.打开 Tomcat Run/Debug configuration 2.打开Deployment标签 3.在“Deploy at Server Startup” 中,移出现有的.war 包 4.点 ...
- win 7 查看端口被占用
开始---->运行---->cmd,或者是window+R组合键,调出命令窗口 输入命令:netstat -ano,列出所有端口的情况.在列表中我们观察被占用的端口,比如是4915 ...
- 孤荷凌寒自学python第三十二天python的代码块中的异常的捕获
孤荷凌寒自学python第三十二天python的代码块中的异常的捕获 (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) 今天简单了解了Python的错误陷阱,了解到其与过去学过的其它语言非常类似 ...
- HDU 3642 Get The Treasury ( 线段树 求长方体体积并 )
求覆盖三次及其以上的长方体体积并. 这题跟 http://wenku.baidu.com/view/d6f309eb81c758f5f61f6722.html 这里讲的长方体体积并并不一样. 因为本题 ...
- 学习go语言第一天
今天先下载了go语言,FQ去下载的,一开始想用eclipse,然后下载了go插件,结果出现错误,我英语水平有限,就换了liteIDE,感觉还不错,go语言环境变量因为我是msi安装的,好像可以不用自己 ...
- 纪中集训总结 && 新学期目标
于是紧接着又发了第二篇. 关于这次去完纪中以后的感想,写完后总觉得少了些什么,因此就发一篇小目标集合来凑数补充一下吧. Part I:图论 这方面我去之前就是很有自信,事实证明像基础的最短路.生成树什 ...
- vue嵌套路由与404重定向实现方法分析
第一部分: vue嵌套路由 嵌套路由是什么? 嵌套路由就是在一个被路由过来的页面下可以继续使用路由,嵌套也就是路由中的路由的意思. 比如在vue中,我们如果不使用嵌套路由,那么只有一个<rout ...