如果你不曾被Python的內置super()函數所折服,很可能是你不知道它能夠做什麼,或者如何有效地使用它。
很多文章寫過super(),但是很多寫的是錯的。此文試着在以下方面進行提高:
- 提供實際用例
- 給一個它如何工作的清楚的模型
- 顯示每次讓它工作的關鍵技術(showing the tradecraft for getting it to work every time)
- 給出在使用super()建立類時的具體建議
- 通過實例而不是抽象的ABCD菱形圖表來說明
class LoggingDict(dict):
def __setitem__(self, key, value):
logging.info('Setting to %r' % (key, value))
super().__setitem__(key, value)
該類與它的父類dict具有相同的功能,但是它擴展了__setitem__方法,使得當一個值更新時有一個log入口。通過建立一個log入口,該方法使用super()函數去委派實際的根據鍵值對去更新字典的工作。
class LoggingDict(SomeOtherMapping): # new base class
def __setitem__(self, key, value):
logging.info('Setting to %r' % (key, value))
super().__setitem__(key, value) # no change needed
除了隔絕改變,computed indirection另一個主要的優勢是,有人可能不熟悉來自靜態語言的人。因爲間接性是在運行時計算的,我們有去影響計算的可能,使得間接性將指向其它的類。
class LoggingOD(LoggingDict, collections.OrderedDict):
pass
對我們的新類,祖先樹爲:LoggingOD, LoggingDict, OrderedDict, dict, object。針對我們的目的,重要的結果是OrderedDict位於LoggingDict之後,dict之前。這意味着super()調用在LoggingDict.__setitem__ 分派key/value更新到OrderedDict,而不是dict。讓我們思考一下。我們並沒有改變LoggingDict的源代碼。而是我們建立了一個子類,該子類的唯一的邏輯就是比較已存在的兩個類並且控制它們的查找順序。
>>> print(LoggingOD.__mro__)
(<class '__main__.LoggingOD'>,
<class '__main__.LoggingDict'>,
<class 'collections.OrderedDict'>,
<class 'dict'>,
<class 'object'>)
如果我們的目的是用我們喜歡的MRO創建一個子類,我們需要知道它是如何被計算的。原則很簡單。順序包括:它本身,它的基類,它的基類的基類,等等,直到到了object對象,這是所有類的基類。該序列的排序使得一個類總是出現在它的父類之前,如果有多個父類的話,它們保持與類定義時父類相同的順序。- LoggingOD先於它的父類,LoggingDict和OrderedDict
- LoggingDict先於OrderedDict,因爲LoggingOD.__bases__是(LoggingDict, OrderedDict)
- LoggingDict先於它的父類,即dict
- OrderedDict先於它的父類,即dict
- dict先於它的父類,即object
- 被super()調用的方法需要存在
- 調用者和被調用者需要有一個匹配的參數標記
- 方法的每一次出現均需要使用super()
class Shape:
def __init__(self, shapename, **kwds):
super.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)看一下使得調用者/被調用者參數匹配的策略,讓我們現在看一下如何確保目標參數存在。
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,這使得調用draw()沒有路徑可以接近object,沒有被Root.draw所阻止。這應該清晰地寫入文檔,是的想要寫新的合作類的人知道子類應該來自於Root。這個限制與Python自己的需求,所有新的異常必須繼承自BaseException,沒有多大的不同,3)上面的技巧確保了super()調用一個方法是知道確實存在的,而且標記也是正確的;但是,我們仍然依賴於super()在每一步被調用,使得委派鏈條持續不會斷裂。如果我們正在合作地設計類,這很容易取得,只是增加了一個super()調用在鏈條的每一個方法中。
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()
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')
這篇文章翻譯自Raymond Hettinger寫的super()的權威文章。
此文對本文翻譯亦有幫助。
此文寫的也特別好。