抽象基類的常見用途:實現接口時作為超類使用。然后,說明抽象基類如何檢查具體子類是否符合接口定義,以及如何使用注冊機制聲明一個類實現了某個接口,而不進行子類化操作。最后,說明如何讓抽象基類自動“識別”任何符合接口的類——不進行子類化或注冊。
Python文化中的接口和協議
接口在動態類型語言中是怎么運作的呢?首先,基本的事實是,Python語言沒有 interface 關鍵字,而且除了抽象基類,每個類都有接口:類實現或繼承的公開屬性(方法或數據屬性),包括特殊方法,如__getitem__ 或 __add__。
按照定義,受保護的屬性和私有屬性不在接口中:即便“受保護的”屬性也只是采用命名約定實現的(單個前導下劃線);私有屬性可以輕松地訪問,原因也是如此。不要違背這些約定。
另一方面,不要覺得把公開數據屬性放入對象的接口中不妥,因為如果需要,總能實現讀值方法和設值方法,把數據屬性變成特性,使用obj.attr 句法的客戶代碼不會受到影響。 Vector2d 類就是這么做的,Vector2d 類的第一版,x 和 y 是公開屬性。
vector2d_v0.py:x 和 y 是公開數據屬性
class Vector2d: def __init__(self, x, y): self.x = x self.y = y def __iter__(self): return (n for n in (self.x, self.y))
我們把 x 和 y 變成了只讀特性。這是一項重大重構,但是 Vector2d 的接口基本沒變:用戶仍能讀取my_vector.x 和 my_vector.y。
class Vector2d: def __init__(self, x, y): self.__x = x self.__y = y @property def x(self): return self.__x @property def y(self): return self.__y def __iter__(self): return (i for i in (self.x, self.y))
Python喜歡序列
Python 數據模型的哲學是盡量支持基本協議。對序列來說,即便是最簡單的實現,Python 也會力求做到最好。
下圖展示了定義為抽象基類的 Sequence 正式接口。

Sequence 抽象基類和 collections.abc 中相關抽象類的UML 類圖,箭頭由子類指向超類,以斜體顯示的是抽象方法
現在,看看下面🌰中的 Foo 類。它沒有繼承 abc.Sequence,而且只實現了序列協議的一個方法: __getitem__ (沒有實現 __len__ 方法)。
>>> class Foo:... def __getitem__(self, pos):... return range(0, 30, 10)[pos]...>>> f = Foo()>>> f[1]>>> for i in f: print(i)...>>> 20 in fTrue>>> 15 in fFalse
雖然沒有 __iter__ 方法,但是 Foo 實例是可迭代的對象,因為發現有__getitem__ 方法時,Python 會調用它,傳入從 0 開始的整數索引,嘗試迭代對象(這是一種后備機制)。盡管沒有實現 __contains__ 方法,但是 Python 足夠智能,能迭代 Foo 實例,因此也能使用 in 運算符:Python 會做全面檢查,看看有沒有指定的元素。
新聞熱點
疑難解答