你真的知道Python的接口是幹嘛的嗎?那麼這些知識點你知道

抽象基類的常見用途:實現接口時作為超類使用。然後,說明抽象基類如何檢查具體子類是否符合接口定義,以及如何使用註冊機制聲明一個類實現了某個接口,而不進行子類化操作。最後,說明如何讓抽象基類自動“識別”任何符合接口的類——不進行子類化或註冊。

如果你還在入門糾結,那小編就把畢身所學都教給你了,哈哈如果你還在苦惱怎麼入門python,小編有個建議,可以加小編弄的一個Python交流基地,大家可以進入交流基地:58937142,裡面新手入門資料,可以說從零到項目實戰,都是可以免費獲取的,還有程序員大牛為各位免費解答問題,熱心腸的小夥伴也是蠻多的。不失為是一個交流的的好地方,小編在這裡邀請大家加入我的大家庭。歡迎你的到來。一起交流學習!共同進步!小編等你!

Python文化中的接口和協議

接口在動態類型語言中是怎麼運作的呢?首先,基本的事實是,Python語言沒有 interface 關鍵字,而且除了抽象基類,每個類都有接口:類實現或繼承的公開屬性(方法或數據屬性),包括特殊方法,如__getitem__ 或 __add__。

按照定義, 受保護的屬性和私有屬性不在接口中 :即便“受保護的”屬性也只是採用命名約定實現的(單個前導下劃線);私有屬性可以輕鬆地訪問,原因也是如此。不要違背這些約定。

另一方面,不要覺得把公開數據屬性放入對象的接口中不妥,因為如果需要,總能實現讀值方法和設值方法,把數據屬性變成特性,使用obj.attr 句法的客戶代碼不會受到影響。 Vector2d 類就是這麼做的,Vector2d 類的第一版,x 和 y 是公開屬性。

vector2d_v0.py:x 和 y 是公開數據屬性

1 class Vector2d:2 3 def __init__(self, x, y):4 self.x = x5 self.y = y6 7 def __iter__(self):8 return (n for n in (self.x, self.y))

我們把 x 和 y 變成了只讀特性。這是一項重大重構,但是 Vector2d 的接口基本沒變:用戶仍能讀取my_vector.x 和 my_vector.y。

1 class Vector2d:

Python喜歡序列

Python 數據模型的哲學是儘量支持基本協議。對序列來說,即便是最簡單的實現,Python 也會力求做到最好。

下圖展示了定義為抽象基類的 Sequence 正式接口。

你真的知道Python的接口是幹嘛的嗎?那麼這些知識點你知道

Sequence 抽象基類和 collections.abc 中相關抽象類的UML 類圖,箭頭由子類指向超類,以斜體顯示的是抽象方法

現在,看看下面:chestnut:中的 Foo 類。它沒有繼承 abc.Sequence,而且只實現了序列協議的一個方法: __getitem__ (沒有實現 __len__ 方法)。

>>> class Foo:...   def __getitem__(self, pos):...     return range(0, 30, 10)[pos]

雖然沒有 __iter__ 方法,但是 Foo 實例是可迭代的對象,因為發現有__getitem__ 方法時,Python 會調用它,傳入從 0 開始的整數索引,嘗試迭代對象(這是一種後備機制)。儘管沒有實現 __contains__ 方法,但是 Python 足夠智能,能迭代 Foo 實例,因此也能使用 in 運算符:Python 會做全面檢查,看看有沒有指定的元素。

綜上,鑑於序列協議的重要性, 如果沒有 __iter__ 和 __contains__方法 ,Python 會 調用 __getitem__ 方法 ,設法讓迭代和 in 運算符可用。

下面的 FrenchDeck 類也沒有繼承 abc.Sequence,但是實現了序列協議的兩個方法:__getitem__ 和 __len__。

1 class FrenchDeck:

使用猴子補丁在運行時實現協議

FrenchDeck 類有個重大缺陷: 無法洗牌 。如果 FrenchDeck 實例的行為像序列,那麼它就不需要 shuffle 方法,因為已經有 random.shuffle 函數可用,文檔中說它的作用是“就地打亂序 x”(https://docs.python.org/3/library/random.html#random.shuffle )。

標準庫中的 random.shuffle 函數用法如下:

>>> from random import shuffle>>> my_list = list(range(1, 11))>>> shuffle(my_list)>>> my_list

然而,如果嘗試打亂 FrenchDeck 實例,會出現異常,如下面的 :chestnut: 所示。

Traceback (most recent call last): File "/Users/demon/PycharmProjects/FluentPython/接口:從協議到抽象基類/FrenchDeck.py", line 37, in <module>

錯誤消息相當明確,“'FrenchDeck' object does not support itemassignment”('FrenchDeck' 對象不支持為元素賦值)。這個問題的原因是, shuffle 函數要調換集合中元素的位置 ,而 F renchDeck 只實現了不可變的序列協議 。 可變的序列 還必須提供 __setitem__ 方法 。

解決辦法為FrenchDeck 打猴子補丁,把它變成可變的,讓random.shuffle 函數能處理

1 from collections import namedtuple 2 from random import shuffle 3

以上代碼的執行結果為:

[Cards(rank='K', suit='diamonds'), Cards(rank='4', suit='spades'), Cards(rank='A', suit='clubs'), Cards(rank='K', suit='spades'), Cards(rank='6', suit='clubs')]

這裡的關鍵是,set_card 函數要知道 deck 對象有一個名為 _cards 的屬性,而且 _cards 的值必須是可變序列。然後,我們把 set_card 函數賦值給特殊方法 __setitem__,從而把它依附到 FrenchDeck 類上。這種技術叫猴子補丁:在運行時修改類或模塊,而不改動源碼。猴子補丁很強大,但是打補丁的代碼與要打補丁的程序耦合十分緊密,而且往往要處理隱藏和沒有文檔的部分。

定義抽象基類的子類

在下面的:chestnut: 中,我們明確把 FrenchDeck2 聲明為collections.MutableSequence 的子類。

frenchdeck2.py:FrenchDeck2,collections.MutableSequence的子類

1 import collections 2

Sequence 和 MutableSequence 抽象基類的方法不全是抽象的。

你真的知道Python的接口是幹嘛的嗎?那麼這些知識點你知道

MutableSequence 抽象基類和 collections.abc 中它的超類的 UML 類圖(箭頭由子類指向祖先;以斜體顯示的名稱是抽象類和抽象方法)

FrenchDeck2 從 Sequence 繼承了幾個拿來即用的具體方法__contains__、__iter__、__reversed__、index 和count。FrenchDeck2 從MutableSequence 繼承了append、extend、pop、remove 和__iadd__。

標準庫中的抽象基類

從 Python 2.6 開始,標準庫提供了抽象基類。大多數抽象基類在collections.abc 模塊中定義,不過其他地方也有。例如,numbers和 io 包中有一些抽象基類。但是,collections.abc 中的抽象基類最常用。我們來看看這個模塊中有哪些抽象基類。

collections.abc模塊中的抽象基類

你真的知道Python的接口是幹嘛的嗎?那麼這些知識點你知道

collections.abc 模塊中各個抽象基類的 UML 類圖

Iterable、Container 和 Sized

各個集合應該繼承這三個抽象基類,或者至少實現兼容的協議。Iterable 通過 __iter__ 方法支持迭代,Container 通過__contains__ 方法支持 in 運算符,Sized 通過 __len__ 方法支持len() 函數。

Sequence、Mapping 和 Set

這三個是主要的不可變集合類型,而且各自都有可變的子類

MappingView

在 Python 3 中,映射方法 .items()、.keys() 和 .values() 返回的對象分別是 ItemsView、KeysView 和 ValuesView 的實例。前兩個類還從 Set 類繼承了豐富的接口。

Callable 和 Hashable

這兩個抽象基類與集合沒有太大的關係,只不過因為collections.abc 是標準庫中定義抽象基類的第一個模塊,而它們又太重要了,因此才把它們放到 collections.abc 模塊中。我從未見過Callable 或 Hashable 的子類。這兩個抽象基類的主要作用是為內置函數 isinstance 提供支持,以一種安全的方式判斷對象能不能調用或散列。

Iterator

注意它是 Iterable 的子類。繼 collections.abc 之後,標準庫中最有用的抽象基類包是numbers

抽象基類的數字塔

numbers 包(https://docs.python.org/3/library/numbers.html )定義的是“數 字塔”(即各個抽象基類的層次結構是線性的),其中 Number 是位於最頂端的超類,隨後是 Complex 子類,依次往下,最底端是 Integral類:

  • Number

  • Complex

  • Real

  • Rational

  • Integral

因此,如果想檢查一個數是不是整數,可以使用 isinstance(x,numbers.Integral) ,這樣代碼就能接受 int、bool(int 的子類),或者外部庫使用 numbers 抽象基類註冊的其他類型。為了滿足檢查的需要,你或者你的 API 的用戶始終可以把兼容的類型註冊為numbers.Integral 的虛擬子類。

與之類似,如果一個值可能是浮點數類型,可以使用 isinstance(x,numbers.Real) 檢查。這樣代碼就能接受bool、int、float、fractions.Fraction,或者外部庫(如NumPy,它做了相應的註冊)提供的非複數類型。

定義並使用一個抽象基類

為了證明有必要定義抽象基類,我們要在框架中找到使用它的場景。想象一下這個場景:你要在網站或移動應用中顯示隨機廣告,但是在整個廣告清單輪轉一遍之前,不重複顯示廣告。假設我們在構建一個廣告管理框架,名為 ADAM。它的職責之一是,支持用戶提供隨機挑選的無重複類。 為了讓 ADAM 的用戶明確理解“隨機挑選的無重複”組件是什麼意思,我們將定義一個抽象基類。

受到“棧”和“隊列”(以物體的排放方式說明抽象接口)啟發,我將使用現實世界中的物品命名這個抽象基類:賓果機和彩票機是隨機從有限的集合中挑選物品的機器,選出的物品沒有重複,直到選完為止。

我們把這個抽象基類命名為 Tombola,這是賓果機和打亂數字的滾動容器的意大利名。

Tombola 抽象基類有四個方法,其中兩個是抽象方法。

  • .load(...):把元素放入容器。

  • .pick():從容器中隨機拿出一個元素,返回選中的元素

另外兩個是具體方法。

  • .loaded():如果容器中至少有一個元素,返回 True。

  • .inspect():返回一個有序元組,由容器中的現有元素構成,不會修改容器的內容(內部的順序不保留)。

展示了 Tombola 抽象基類和三個具體實現。

你真的知道Python的接口是幹嘛的嗎?那麼這些知識點你知道

一個抽象基類和三個子類的 UML 類圖。根據 UML 的約定,Tombola 抽象基類和它的抽象方法使用斜體。虛線箭頭用於表示接口實現,這裡它表示 TomboList 是 Tombola 的虛擬子類,因為TomboList 是註冊的

1 import abc 2

選擇使用 LookupError 異常的原因是,在 Python 的異常層次關係中,它與 IndexError 和 KeyError 有關,這兩個是具體實現 Tombola的數據結構最有可能拋出的異常。據此,實現代碼可能會拋出LookupError、IndexError 或 KeyError 異常。

異常類的部分層次結構

BaseException├── SystemExit

我們自己定義的 Tombola 抽象基類完成了。為了一睹抽象基類對接口所做的檢查,下面我們嘗試使用一個有缺陷的實現來糊弄 Tombola,如下面的 :chestnut: 所示

不符合 Tombola 要求的子類無法矇混過關

>>> from tombola import Tombola>>> class Fake(Tombola):               # 把Fake聲明為Tombole的子類,繼承抽象類... def pick(self):

抽象基類句法詳解

聲明抽象基類最簡單的方式是繼承 abc.ABC 或其他抽象基類。

然而,abc.ABC 是 Python 3.4 新增的類,因此如果你使用的是舊版Python,那麼無法繼承現有的抽象基類。此時,必須在 class 語句中使用 metaclass= 關鍵字,把值設為 abc.ABCMeta(不是 abc.ABC)。在下面的:chestnut: ,可以寫成:

class Tombola(metaclass=abc.ABCMeta):

metaclass= 關鍵字參數是 Python 3 引入的。在 Python 2 中必須使用__metaclass__ 類屬性:

class Tombola(object): # 這是Python 2!!!

除了 @abstractmethod 之外,abc 模塊還定義了 @abstractclassmethod、@abstractstaticmethod 和@abstractproperty 三個裝飾器 。然而, 後三個裝飾器從 Python 3.3起廢棄了 ,因為裝飾器可以在 @abstractmethod 上堆疊,那三個就顯得多餘了。例如,聲明抽象類方法的推薦方式是:

class MyABC(abc.ABC): @classmethod @abc.abstractmethod

定義Tombola抽象基類的子類

定義好 Tombola 抽象基類之後,我們要開發兩個具體子類,滿足Tombola 規定的接口。

下面的:chestnut: 中的 BingoCage 類,使用了更好的隨機發生器。 BingoCage 實現了所需的抽象方法 load 和 pick,從 Tombola 中繼承了 loaded 方法,覆蓋了 inspect 方法,還增加了__call__ 方法。

1 import random 2 import abc 3

下面是 Tombola 接口的另一種實現,雖然與之前不同,但完全有效。LotteryBlower 打亂“數字球”後沒有取出最後一個,而是取出一個隨機位置上的球。

lotto.py:LotteryBlower 是 Tombola 的具體子類,覆蓋了繼承的 inspect 和 loaded 方法

1 import random 2

相關推薦

推薦中...