Python’s super() considered super!¶
参考:
class LoggingDict(dict):
def __setitem__(self, key, value):
logging.info('Settingto %r' % (key, value))
super().__setitem__(key, value)
在父类 dict 的基础上添加了 log 消息,然后通过调用父类的方法完成实际的工作。这种间接调用的一个好处是如果修改了基类,那么 super 访问的基类会随之改变。另一个好处是在动态语言中,因为行为是运行时决定的,所以可以在运行时修改类的行为。
对于类行为的影响因素有两个:super 所在的类和继承树。第一点,super 所在的类,是由类代码决定的。第二个方面,就比较有趣了。在不改变原有类的情况下来构造一个 logging ordered dict
class LoggingOD(LoggingDict, collections.OrderedDict):
pass
新类的继承树是:LoggingOD, LoggingDict, OrderedDict, dict, object 重要的是 OrderedDict 在 LoggingDict 前,在 dict 后。这意味着 LoggingDict.__setitem__
里面的 super 现在修改的是 OrderedDict 而不是 dict。
仅仅通过新增一个继承关系就能改变原有类的行为方式,想想就让人激动。
Search Order¶
MRO 的生成遵循 C3 算法
Practical Advice¶
三条实用建议:
- super 调用的方法必须存在
- 调用和被调用函数签名必须匹配
- 每个方法的调用都必须使用 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')
- 确保 super 调用的方法存在
对于 object 中有的方法,所有的 MRO 最终都会指向 object,所以最终会调用 object 中的相应方法,没有问题。对于 object 没有对应方法的函数,可以些一个 Root 类,将相应方法在 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()
如果需要将其他类植入 MRO 那么需要确保这个类也要继承 Root,这种技术就和所有的 exceptions 都要从 BaseException 继承一样。
- 对于合作类,确保函数都通过 super 调用不是问题
How to Incorporate a Non-cooperative Class¶
有的时候,需要基于非合作的类来定制我们的代码,这时可以写一个 adapter class 比如有这样一个非合作类
class Moveable:
def __init__(self, x, y):
self.x = x
self.y = y
def draw(self):
print('Drawing at position:', self.x, self.y)
重写 adapter
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()
Complete Example – Just for Fun¶
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')
Notes and References¶
- 当继承一个 builtin 的时候,通常需要同时重载或扩展多个方法。比如在上面的例子中,
__setitem__
扩展就没有被其他如 dict.update 的方法使用,所以可能把这些方法也扩展一下比较好。这个问题并不是 super 独有的,而是继承 builtin 时普遍存在的。 - 如果类存在嵌套继承,而我们需要确定的继承顺序,使用 assert 是个很好的选在,配合 index 方法
position = LoggingOD.__mro__.index
assert position(LoggingDict) < position(OrderedDict)
assert position(OrderedDict) < position(dict)
完整示例:
'Demonstrate effective use of super()'
import collections
import logging
logging.basicConfig(level='INFO')
class LoggingDict(dict):
# Simple example of extending a builtin class
def __setitem__(self, key, value):
logging.info('Setting %r to %r' % (key, value))
super().__setitem__(key, value)
class LoggingOD(LoggingDict, collections.OrderedDict):
# Build new functionality by reordering the MRO
pass
ld = LoggingDict([('red', 1), ('green', 2), ('blue', 3)])
print(ld)
ld['red'] = 10
ld = LoggingOD([('red', 1), ('green', 2), ('blue', 3)])
print(ld)
ld['red'] = 10
print('-' * 20)
# ------- Show the order that the methods are called ----------
def show_call_order(cls, methname):
'Utility to show the call chain'
classes = [cls for cls in cls.__mro__ if methname in cls.__dict__]
print(' ==> '.join('%s.%s' % (cls.__name__, methname) for cls in classes))
show_call_order(LoggingOD, '__setitem__')
show_call_order(LoggingOD, '__iter__')
print('-' * 20)
# ------- Validate and document any call order requirements -----
position = LoggingOD.__mro__.index
assert position(LoggingDict) < position(collections.OrderedDict)
assert position(collections.OrderedDict) < position(dict)
# ------- Getting the argument signatures to match --------------
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')
# -------- Making sure a root exists ----------------------------
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()
ColoredShape(color='blue', shapename='square').draw()
print('-' * 20)
# ------- Show how to incorporate a non-cooperative class --------
class Moveable:
# non-cooperative class that doesn't use super()
def __init__(self, x, y):
self.x = x
self.y = y
def draw(self):
print('Drawing at position:', self.x, self.y)
class MoveableAdapter(Root):
# make a cooperative adapter class for Moveable
def __init__(self, *, x, y, **kwds):
self.moveable = Moveable(x, y)
super().__init__(**kwds)
def draw(self):
self.moveable.draw()
super().draw()
class MovableColoredShape(ColoredShape, MoveableAdapter):
pass
MovableColoredShape(color='red', shapename='triangle', x=10, y=20).draw()
# -------- Complete example ------------------------------------
from collections import Counter, OrderedDict
class OrderedCounter(Counter, OrderedDict):
'Counter that remembers the order elements are first encountered'
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, OrderedDict(self))
def __reduce__(self):
return self.__class__, (OrderedDict(self),)
oc = OrderedCounter('abracadabra')
print(oc)