1. 程式人生 > >python與鴨子類型

python與鴨子類型

file pytho valid col 離開 多個 關註 產生 err

部分參考來源:作者:JasonDing https://www.jianshu.com/p/650485b78d11##s1

首先介紹下面向對象(OOP)的三大特征:

(1)面向對象程序設計有三大特征:封裝(Encapsulation)、繼承(Inheritance)、多態(Polymorphism)。這三個單詞很常見,大家還是記住為好!

(2)封裝(Encapsulation):類包含了數據和方法,將數據和方法放在一個類中就構成了封裝。

(3)繼承(Inheritance):Java是單繼承的(這點和C++有區別),意味著一個類只能繼承於一個類,被繼承的類叫父類(或者叫基類,base class),繼承的類叫子類。Java中的繼承使用關鍵字extends。但是,一個類可以實現多個接口,多個接口之間用逗號進行分割。實現接口使用關鍵字implements。

(4)多態(Polymorphism):多態最核心的思想就是,父類的引用可以指向子類的對象,或者接口類型的引用可以指向實現該接口的類的實例。多態之所以是這樣的是因為基於一個事實:子類就是父類!

(5)關於多態的一些重要說明:

  • 當使用多態方式調用方法時,首先檢查父類中是否有此方法,如果沒有則編譯錯誤,如果有則再去調用子類重寫(Override)【如果重寫的話】的此方法,沒有重寫的話,還是調用從父類繼承過來的方法。
  • 兩種類型的強制類型轉換:
    1. 向上類型轉換(upcast):將子類型引用轉換成父類型引用。對於向上類型轉換不需要顯示指定。
    2. 向下類型轉換(downcast):將父類型引用轉換成子類型引用。對於向下類型轉換,必須要顯示指定。向下類型轉換的原則:父類型引用指向誰才能轉換成誰。
  • 多態是一種運行期的行為,不是編譯期行為!在編譯期間它只知道是一個引用,只有到了執行期,引用才知道指向的是誰。這就是所謂的“軟綁定”。
  • 多態是一項讓程序員“將改變的事物和未改變的事物分離開來”重要技術。

鴨子類型:

調用不同的子類將會產生不同的行為,而無須明確知道這個子類實際上是什麽,這是多態的重要應用場景。而在python中,因為鴨子類型(duck typing)使得其多態不是那麽酷。
鴨子類型是動態類型的一種風格。在這種風格中,一個對象有效的語義,不是由繼承自特定的類或實現特定的接口,而是由"當前方法和屬性的集合"決定。這個概念的名字來源於由James Whitcomb Riley提出的鴨子測試,“鴨子測試”可以這樣表述:“當看到一只鳥走起來像鴨子、遊泳起來像鴨子、叫起來也像鴨子,那麽這只鳥就可以被稱為鴨子。”
在鴨子類型中,關註的不是對象的類型本身,而是它是如何使用的。例如,在不使用鴨子類型的語言中,我們可以編寫一個函數,它接受一個類型為"鴨子"的對象,並調用它的"走"和"叫"方法。在使用鴨子類型的語言中,這樣的一個函數可以接受一個任意類型的對象,並調用它的"走"和"叫"方法。如果這些需要被調用的方法不存在,那麽將引發一個運行時錯誤。任何擁有這樣的正確的"走"和"叫"方法的對象都可被函數接受的這種行為引出了以上表述,這種決定類型的方式因此得名。
鴨子類型通常得益於不測試方法和函數中參數的類型,而是依賴文檔、清晰的代碼和測試來確保正確使用。

Duck typing 這個概念來源於美國印第安納州的詩人詹姆斯·惠特科姆·萊利(James Whitcomb Riley,1849-
1916)的詩句:”When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.”
先上代碼,也是來源於網上很經典的案例:

 1 class Duck():
 2     def walk(self):
 3         print(I walk like a duck)
 4     def swim(self):
 5         print(i swim like a duck)
 6 
 7 class Person():
 8     def walk(self):
 9       print(this one walk like a duck) 
10     def swim(self):
11       print(this man swim like a duck)

可以很明顯的看出,Person類擁有跟Duck類一樣的方法,當有一個函數調用Duck類,並利用到了兩個方法walk()swim()。我們傳入Person類也一樣可以運行,函數並不會檢查對象的類型是不是Duck,只要他擁有walk()swim()方法,就可以正確的被調用。
再舉例,如果一個對象實現了__getitem__方法,那python的解釋器就會把它當做一個collection,就可以在這個對象上使用切片,獲取子項等方法;如果一個對象實現了__iter__next方法,python就會認為它是一個iterator,就可以在這個對象上通過循環來獲取各個子項。

python中的多態

python中的鴨子類型允許我們使用任何提供所需方法的對象,而不需要迫使它成為一個子類。
由於python屬於動態語言,當你定義了一個基類和基類中的方法,並編寫幾個繼承該基類的子類時,由於python在定義變量時不指定變量的類型,而是由解釋器根據變量內容推斷變量類型的(也就是說變量的類型取決於所關聯的對象),這就使得python的多態不像是c++或java中那樣,定義一個基類類型變量而隱藏了具體子類的細節。

請看下面的例子和說明:

 1 class AudioFile:
 2     def __init__(self, filename):
 3         if not filename.endswith(self.ext):
 4             raise Exception("Invalid file format")
 5         self.filename = filename
 6 
 7 class MP3File(AudioFile):
 8     ext = "mp3"
 9     def play(self):
10         print("Playing {} as mp3".format(self.filename))
11 
12 class WavFile(AudioFile):
13     ext = "wav"
14     def play(self):
15         print("Playing {} as wav".format(self.filename))
16 
17 class OggFile(AudioFile):
18     ext = "ogg"
19     def play(self):
20         print("Playing {} as ogg".format(self.filename))
21 
22 class FlacFile:
23     """
24     Though FlacFile class doesn‘t inherit AudioFile class,
25     it also has the same interface as three subclass of AudioFile.
26 
27     It is called duck typing.
28     """
29     def __init__(self, filename):
30         if not filename.endswith(".flac"):
31             raise Exception("Invalid file format")
32         self.filename = filename
33 
34     def play(self):
35         print("Playing {} as flac".format(self.filename))
上面的代碼中,MP3FileWavFileOggFile三個類型繼承了AudioFile這一基類,而FlacFile沒有擴展AudioFile,但是可以在python中使用完全相同的接口與之交互。
因為任何提供正確接口的對象都可以在python中交替使用,它減少了多態的一般超類的需求。繼承仍然可以用來共享代碼,但是如果所有被共享的都是公共接口,鴨子類型就是所有所需的。這減少了繼承的需要,同時也減少了多重繼承的需要;通常,當多重繼承似乎是一個有效方案的時候,我們只需要使用鴨子類型去模擬多個超類之一(定義和那個超類一樣的接口和實現)就可以了。

python與鴨子類型