深入思考python的super()
0. 背景
本文翻译自文章 《Python’s super() considered super!》,这篇文章是博主在王晓斌博客中,看到的博客Python面向对象_super()函数。博客中的一个问题:
class Child(Parent):
def __init__(self):
Parent.__init__(self)
这种方式与super(Child, self).__init__()有区别么?
博客中特别推荐了《Python’s super() considered super!》这篇文章,因此博主翻译了一下,以下是正文,供大家一起交流。有翻译不到位之处,不吝指正。
1. 前言
如果你对python的内建函数—super()的威力并不惊叹,那你可能也不太清楚它到底能够做什么,或是如何高效地使用它。虽然有好多关于super()的阐述,但是其中也不乏失败的文章。本文希望能通过以下几点中寻求突破:
- 举出一些实用的例子;
- 提供一个清晰形象的模型,介绍super()是如何工作的;
- 展示细腻的技术指南,确保每次都能使super()工作;
- 在使用super()建立新类时,给出具体的建议;
- 为经典的ABCD类的多继承例图,提供有帮助的案例。
class LoggingDict(dict):
def __setitem__(self, key, value):
logging.info('Settingto %r' % (key, value))
super().__setitem__(key, value)
class LoggingDict(SomeOtherMapping): # new base class
def __setitem__(self, key, value):
logging.info('Settingto %r' % (key, value))
super().__setitem__(key, value) # no change needed
class LoggingOD(LoggingDict, collections.OrderedDict):
pass
2. 搜索顺序
>>> print(LoggingOD.__mro__) (<class '__main__.LoggingOD'>, <class '__main__.LoggingDict'>, <class 'collections.OrderedDict'>, <class 'dict'>, <class 'object'>)
- LoggingOD 先于它的父类,LoggingDict和OrderedDict
- LoggingDict先于OrderedDict,因为LoggingDict.__bases__是元组(LoggingDict, OrderedDict)
- LoggingDict先于它的父类dict
- OrderedDict先于它的父类dict
- dict先于它的父类object
3. 实践建议
- 通过MRO,super()要调用的方法必须要存在;
- 调用者和被调用者的函数签名必须一致;
- 在依据MRO得到的调用方法链上,每一处都需要调用super()
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__方法,并且object类往往是MRO链中的末端,所以任何一个新式类调用super().__init(),最终都会调到object.__init__方法的。换句话说,我们必须保证目标类的super()调用的MRO中的一系列类方法都是存在的,而不会抛出AttributeError的异常。
对于那些object没有的方法,比如ColoredShape.draw()方法,我们就需要写一个root类来保证在object类之前能够调用。root类的职责“吃”进某个方法(比如draw()),且不再使用super()向后调用。
Root.draw方法也要负责做一些容错处理,例如使用断言(assert)来确保MRO链中root类后的类(比如object类)没有draw方法。如果某个子类“阴差阳错”地继承了某个父类,这个父类正好也有draw()方法,而不继承root类,这个这种容错处理是有必要的。
class Root:
def draw(self):
# the delegation chain stops here
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()
4. 如何与“不理想”类合作
class Moveable:
def __init__(self, x, y):
self.x = x
self.y = y
def draw(self):
print('Drawing at position:', self.x, self.y)
如果我们想要使用这个Moveable类,并把它放入ColoredShape类的MRO中,我们就可以创建一个适配类MoveableAdapter来准守使用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()
5. 完整例子-开心就好 # 博主认为与本文关系不大:)
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')
6. 备注和文献
position = LoggingOD.__mro__.index assert position(LoggingDict) < position(OrderedDict) assert position(OrderedDict) < position(dict)
5. 上述例子的源码请参考Recipe 577720,它均采用python3的语法写的,python2.x的super()与python3的有些不一样,它是把super()方法内的参数列表显式的表达出来,比如super(obj, type),而且

浙公网安备 33010602011771号