鴨子類型基本定義
首先Python不支持多態(tài),也不用支持多態(tài),python是一種多態(tài)語言,崇尚鴨子類型。
以下是維基百科中對鴨子類型得論述:
在程序設計中,鴨子類型(英語:duck typing)是動態(tài)類型的一種風格。在這種風格中,一個對象有效的語義,不是由繼承自特定的類或?qū)崿F(xiàn)特定的接口,而是由當前方法和屬性的集合決定。這個概念的名字來源于由James Whitcomb Riley提出的鴨子測試,“鴨子測試”可以這樣表述:
“當看到一只鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那么這只鳥就可以被稱為鴨子?!?br/>
在鴨子類型中,關注的不是對象的類型本身,而是它是如何使用的。例如,在不使用鴨子類型的語言中,我們可以編寫一個函數(shù),它接受一個類型為鴨的對象,并調(diào)用它的走和叫方法。在使用鴨子類型的語言中,這樣的一個函數(shù)可以接受一個任意類型的對象,并調(diào)用它的走和叫方法。如果這些需要被調(diào)用的方法不存在,那么將引發(fā)一個運行時錯誤。任何擁有這樣的正確的走和叫方法的對象都可被函數(shù)接受的這種行為引出了以上表述,這種決定類型的方式因此得名。
鴨子類型通常得益于不測試方法和函數(shù)中參數(shù)的類型,而是依賴文檔、清晰的代碼和測試來確保正確使用。從靜態(tài)類型語言轉(zhuǎn)向動態(tài)類型語言的用戶通常試圖添加一些靜態(tài)的(在運行之前的)類型檢查,從而影響了鴨子類型的益處和可伸縮性,并約束了語言的動態(tài)特性。
python中的具體實現(xiàn)
下面的代碼就是一個簡單的鴨子類型
class duck(): def walk(self): print('I walk like a duck') def swim(self): print('i swim like a duck') class person(): def walk(self): print('this one walk like a duck') def swim(self): print('this man swim like a duck')
對于一個鴨子類型來說,我們并不關心這個對象的類型本身或是這個類繼承,而是這個類是如何被使用的。我們可以通過下面的代碼來調(diào)用這些類的方法。
def watch_duck(animal): animal.walk() animal.swim() small_duck = duck() watch_duck(small_duck) output >> I walk like a duck i swim like a duck duck_like_man = person() watch_duck(duck_like_man) output >> this one walk like a duck this man swim like a duck class Lame_Foot_Duck(): def swim(self): print('i am lame but i can swim') lame_duck = Lame_Foot_Duck() watch_duck(lame_duck) output >> AttributeError: Lame_Foot_Duck instance has no attribute 'walk'
watch_duck
函數(shù)接收這個類的對象,然后并沒有檢查對象的類型,而是直接調(diào)用這個對象的走和游的方法,如果所需要的方法不存在就報錯。
具體在python中鴨子類型的體現(xiàn)如下面的代碼所示
class CollectionClass(): lists = [1,2,3,4] def __getitem__(self, index): return self.lists[index] iter_able_object = CollectionClass() class Another_iterAbleClass(): lists=[1,2,3,4] list_position = -1 def __iter__(self): return self def next(self): #還有更簡單的實現(xiàn),使用生成器或迭代器什么的:) self.list_position += 1 if self.list_position >3: raise StopIteration return self.lists[self.list_position] another_iterable_object=Another_iterAbleClass() print(iter_able_object[1]) print(iter_able_object[1:3]) output>> 2 [2, 3] another_iterable_object[2] output>> Traceback (most recent call last): File "/Users/steinliber/a.py", line 32, in <module> another_iterable_object[2] TypeError: 'Another_iterAbleClass' object does not support indexing print(next(another_iterable_object)) output>> 1 print(next(another_iterable_object)) output>> 2 print(next(iter_able_object)) output>> Traceback (most recent call last): File "/Users/steinliber/a.py", line 29, in <module> print(next(iter_able_object)) TypeError: IterAbleClass object is not an iterator
在python把上述代碼的實現(xiàn)方法叫做protocol(協(xié)議),這些protocol可以看作是通知型的接口,它規(guī)定了調(diào)用方使用該功能要調(diào)用對象的哪些方法,被調(diào)用方要實現(xiàn)哪些方法才能完成這個功能。它和java中的接口區(qū)別在于java中的接口功能實現(xiàn)需要通過繼承,繼承的類必須實現(xiàn)接口中的所有的抽象方法,所以在Java中強調(diào)的是類型的概念,而python中的protocol
更多的是通知性的,一個函數(shù)規(guī)定要實現(xiàn)某個功能需要調(diào)用傳入對象的哪些方法,所有實現(xiàn)這些方法的類就可以實現(xiàn)這個功能。
具體從上面兩個類來說,第一個類實現(xiàn)了__getitem__
方法,那python的解釋器就會把它當做一個collection,就可以在這個類的對象上使用切片,獲取子項等方法,第二個類實現(xiàn)了__iter__
和next
方法,python就會認為它是一個iterator,就可以在這個類的對象上通過循環(huán)來獲取各個子項。一個類可以實現(xiàn)它有能力實現(xiàn)的方法,并只能被用于在它有意義的情況下。
這兩個類和上面的鴨子類相比較,其實用于切邊的[](它其實調(diào)用的是python的slice
函數(shù))和用于循環(huán)的iter()
就相當于watch_duck
函數(shù),這些函數(shù)都接收任意類的對象,并調(diào)用實現(xiàn)功能所需要的對象中的方法來實現(xiàn)功能,若該函數(shù)中調(diào)用的方法對象里面不存在,就報錯。
從上面可以看出,python鴨子類型的靈活性在于它關注的是這個所調(diào)用的對象是如何被使用的,而沒有關注對象類型的本身是什么。所以在python中使用isinstance
來判斷傳入?yún)?shù)的類型是不提倡的,更pythonic
的方法是直接使用傳入的參數(shù),通過try,except
來處理傳入?yún)?shù)不符合要求的情況。我們應該通過傳入對象的能力而不是傳入對象的類型來使用該對象。
總結(jié)
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com