非常牛的參考文章:Python’s super() considered super
介紹#
眾所周知,Python作為高級語言,既支持單繼承,且支持多繼承。在單繼承中,如果子類想調用父類,可以使用super()。
官方解釋:super()返回將方法調用委托給類型的父類或同級類的代理對象。 這對於訪問已在類中重寫的繼承方法很有用。
原型:
Init signature: super(self, /, *args, **kwargs)
Docstring:
super() -> same as super(__class__, <first argument>)
super(type) -> unbound super object
super(type, obj) -> bound super object; requires isinstance(obj, type)
super(type, type2) -> bound super object; requires issubclass(type2, type)
Typical use to call a cooperative superclass method:
class C(B):
def meth(self, arg):
super().meth(arg)
This works for class methods too:
class C(B):
@classmethod
def cmeth(cls, arg):
super().cmeth(arg)
Type: type
Subclasses:
通常在兩種情況下使用該函數:
- 在具有單一繼承的類層次結構中,super可以用於引用父類而無需顯式命名它們,從而使代碼更具可維護性。 這種用法與其他編程語言中super的用法非常相似。
- 第二個用例是在動態執行環境中支持協作式多重繼承。 這種方式是Python獨有的,在靜態編譯語言或僅支持單繼承的語言中找不到。 這使得在多個基類實現相同方法的情況下實現“菱形圖”成為可能。 良好的設計要求該方法在每種情況下都具有相同的調用簽名(因為調用的順序是在運行時確定的,因為該順序適合於類層次結構中的更改,並且因為該順序可以包含在運行時之前未知的同級類 )。
還要注意,除零參數形式外,super()不限於在內部方法中使用。 兩個參數形式准確地指定了參數並進行了適當的引用。 零參數形式僅在類定義內起作用,因為編譯器會填寫必要的詳細信息以正確檢索要定義的類,以及為常規方法訪問當前實例。
一般用法-單繼承#
單繼承,子類繼承builtin類:
import logging
class LoggingDict(dict):
def __setitem__(self, key, value):
logging.info('Setting %r to %r' % (key, value))
super().__setitem__(key, value)
LoggingDict繼承Python內置dict的所有功能,但是它擴展了__setitem__方法,以便在更新key時進行日志輸入。 輸入日志后,該方法使用super()委派工作以實際更新具有鍵/值對的字典。
在super()引入Python之前,如果我們想調用父類中的方法,通常我們會這樣寫:dict.setitem(self, key, value)。這種硬編碼的方式引入了難以維護的代碼,比如當LoggingDict修改繼承的父類為SomeOtherMapping時,調用父類的方法就會變為SomeOtherMapping.setitem(self, key, value)。
使用super()更好的原因是super是一個計算的間接引用。意味着實際委托的父類或繼承類可以在類繼承層次結構中動態確定。
間接的好處之一是我們不必按名稱指定委托類。 如果編輯源代碼以將基類切換到其他類時,則super()引用將自動跟隨。
比如上面的代碼示例換成其他類繼承時,代碼其他部分不需要改變:
class LoggingDict(SomeOtherMapping): # new base class
def __setitem__(self, key, value):
logging.info('Setting %r to %r' % (key, value))
super().__setitem__(key, value) # no change needed
#進階用法-多繼承# 除了使代碼更易於維護之外,間接引用( computed indirection)繼承類還具有另一個主要優點,這可能是靜態語言的人們可能不熟悉的。 由於間接引用( computed indirection)是在運行時得到的,因此我們可以自由地影響間接引用的計算,以便間接指向其他類。 計算取決於調用super的類和當前類的祖先樹。 第一個組件是調用super的類,由該類的源代碼確定。 在我們的示例中,在LoggingDict .__ setitem__方法中調用了super()。 該部分是固定的。 第二個也是更有趣的部分是變量(我們可以使用祖先樹來創建新的子類)。 如下所示:
from collections import OrderedDict
class LoggingOD(LoggingDict, OrderedDict):
pass
>>> printLoggingOD.__mro__)
(<class '__main__.LoggingOD'>, <class '__main__.LoggingDict'>, <class 'collections.OrderedDict'>, <class 'dict'>, <class 'object'>)
類的__mro__屬性:此屬性是在方法解析期間尋找基類時要考慮的類的元組。舉例來說,就是LoggingOD的實例在調用某些方法時的查找順序。mro全稱為Method Resolution Order,使用的算法為著名的C3算法,詳情可見:The Python 2.3 Method Resolution Order。
仔細考慮下上面的代碼,我們沒有更改LoggingDict的源代碼。 相反,我們建立了一個子類LoggingOD,其唯一邏輯是繼承兩個現有類並控制它們的搜索順序。
###Search Order### 如果我們的目標是根據自己的喜好創建帶有MRO的子類,則我們需要知道如何計算它。 基礎很簡單。 序列包括該類,其基類以及這些基類的基類,依此類推,直到到達object(object是所有類的根類)為止。 該順序是有序的,以便一個類始終出現在其父級之前,並且如果有多個父級,則它們與基類的元組保持相同的順序。 The MRO shown above is the one order that follows from those constraints: - LoggingOD precedes its parents, LoggingDict and OrderedDict - LoggingDict precedes OrderedDict because LoggingOD.__bases__ is (LoggingDict, OrderedDict) - LoggingDict precedes its parent which is dict - OrderedDict precedes its parent which is dict - dict precedes its parent which is object
解決這些約束的過程稱為線性化。 關於此主題有很多不錯的文章,但是要創建我們喜歡的帶有MRO的子類,我們只需要知道兩個約束:子類先於父類,並且基於__bases__中出現的順序。
實用建議###
super()負責將方法調用委派給實例的祖先樹中的某個類。 為了使可重排序的方法調用起作用,需要協同設計類。 這提出了三個容易解決的實際問題:
- 被super()調用的方法必須存在
- 調用者和被調用者必須有相同的方法參數簽名
- 並且該方法的每次出現都需要使用super()
1). 首先,讓我們看一下獲取調用者參數以匹配被調用方法簽名的策略。 這比事先已知被調用的傳統方法調用更具挑戰性。 使用super(),在編寫類時不知道被調用方(因為稍后編寫的子類可能會將新的類引入MRO中)。
一種方法是堅持使用位置參數的固定簽名。 這對於__setitem__之類的方法具有很好的效果,該方法具有兩個參數(一個key和一個value)的固定簽名。 LoggingDict示例中顯示了此實現,其中__setitem__在LoggingDict和dict中具有相同的簽名。
一種更靈活的方法是使祖先樹中的每個方法都經過協作設計,以接受關鍵字參數和關鍵字參數字典,以刪除所需的任何參數,並使用**kwds轉發其余參數,最終使字典在鏈中進行最后的調用時為空。
每一次調用都剝離其所需的關鍵字參數,以便最終的空dict可以發送到根本不需要參數的方法(例如,object .__ init__希望無任何參數(self除外)):
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')
>>> print(cs.__dict__)
{'color': 'red', 'shapename': 'circle'}
2). 在研究了使調用者/被調用者參數模式匹配的策略之后,現在讓我們看一下如何確保目標方法存在。
上面的示例顯示了最簡單的情況。 我們知道object具有_init_方法,並且object始終是MRO鏈中的最后一個類,因此對super(). init__的任何調用序列都以對object .__ init__方法的調用結尾。 換句話說,我們保證super()調用的對象是存在的,並且不會因AttributeError而失敗。
對於object沒有感興趣的方法的情況(例如draw()方法),我們需要編寫一個root類,該root類必須保證在object之前被調用。 root類的職責只是吃掉方法調用,而無需使用super()進行轉發調用。
Root.draw也可以使用斷言進行defensive programming,以確保它不會在鏈的后面掩蓋其他一些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()
Drawing. Setting color to: blue
Drawing. Setting shape to: square
如果子類希望將其他類注入MRO,則這些其他類也需要從Root繼承,確保調用draw()的路徑如果在Root.draw處終止,就無法到達object。。 應該清楚地記錄下來,以便編寫新的協同類的人知道從Root繼承子類。 該限制與Python自己的要求(所有新exceptions必須繼承自BaseException)沒有太大不同。
3). 上面顯示的技術確保super()調用已知存在的方法,並且簽名正確。 但是,我們仍然依靠在每個步驟都調用super()來使委托鏈不中斷。 如果我們要設計協作類,這很容易實現–只需向鏈中的每個方法添加一個super()調用即可。
上面列出的三種技術提供了設計可以由子類組成或重新排序的協作類的方法。
非協作類納入協作類###
有時,子類可能希望與非專為該類設計的第三方類一起使用協作式多重繼承技術(也許其感興趣的方法未使用super()或該類未從root類繼承)。 通過創建按規則的適配器類(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)
例如,上面Moveable類不進行super()調用,並且具有與object .__ init__不兼容的__init __()方法簽名,並且不繼承自Root。
如果要在我們的協作式類ColoredShape層次結構中使用Moveable,則需要使用必需的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
>>> print(MoveableColoredShape.__mro__)
(<class '__main__.MoveableColoredShape'>, <class '__main__.ColoredShape'>, <class '__main__.Shape'>, <class '__main__.MoveableAdapter'>, <class '__main__.Root'>, <class 'object'>)
>>> mcs = MoveableColoredShape(color='red', shapename='triangle', x=10, y=20)
>>> mc.draw()
Drawing. Setting color to: red
Drawing. Setting shape to: triangle
Drawing at position: 10 20
#完整示例# 在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')
>>> OrderedCounter(OrderedDict([('a', 5), ('b', 2), ('r', 2), ('c', 1), ('d', 1)]))
OrderedCounter(OrderedDict([('a', 5), ('b', 2), ('r', 2), ('c', 1), ('d', 1)]))
