Python's super() provides a unique and amazing capability. It allows subclasses to be written to reorder a chain method calls. The recipe demonstrates all of the tradecraft needed to get super() to do your bidding.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | '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)
|
For an explanation what each piece of code is doing and why it is necessary, see http://rhettinger.wordpress.com/2011/05/26/super-considered-super/