python與鴨子類型
部分參考來源:作者: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)【如果重寫的話】的此方法,沒有重寫的話,還是調用從父類繼承過來的方法。
- 兩種類型的強制類型轉換:
- 向上類型轉換(upcast):將子類型引用轉換成父類型引用。對於向上類型轉換不需要顯示指定。
- 向下類型轉換(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))上面的代碼中,
MP3File
、WavFile
、OggFile
三個類型繼承了AudioFile
這一基類,而FlacFile
沒有擴展AudioFile
,但是可以在python中使用完全相同的接口與之交互。因為任何提供正確接口的對象都可以在python中交替使用,它減少了多態的一般超類的需求。繼承仍然可以用來共享代碼,但是如果所有被共享的都是公共接口,鴨子類型就是所有所需的。這減少了繼承的需要,同時也減少了多重繼承的需要;通常,當多重繼承似乎是一個有效方案的時候,我們只需要使用鴨子類型去模擬多個超類之一(定義和那個超類一樣的接口和實現)就可以了。
python與鴨子類型