眾所周知,Python作為膠水語言,它可以做的東西很多,爬蟲、人工智能、自動化測試、數據分析等等。而鴨子是一種動物,它可以做的東西也很多,啤酒鴨、香烤鴨、鹽水鴨、土豆燜鴨等等。按理說這兩個對應著不同人體器官的東西應該是扯不上關系的。
但是,偏偏就有辣莫一個人,美國詩人詹姆斯·惠特科姆·萊利,在17世紀時寫下了一句詩:
「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.」
就是這短短的一句詩,讓這兩者扯上了神奇的關系,關鍵人們還為這種關系取了個名字 -- 鴨子類型。從此Python和鴨子就成就了一段佳話啊呸,那這 鴨子類型究竟是怎么回事呢?且往下看~
思考一個場景
加入在你擁有一款內容聚合應用,這款應用每天會從各個門戶網站采集一些文章回來,并且分發至應用里面的各個頻道。
這個時候我們可以將分發文章這個功能簡單的抽象為一個distribute函數,該函數由兩個參數構成,待分發文章article,分發頻道channel。
同時為了保證文章更符合頻道的內容范圍和調性,在每篇文章分發至頻道時,最好都對文章做一些準入校驗,于是我們初步封裝出以下函數:
def distribute(article, channel): # 文章準入判斷 # 政務頻道的文章標題不能出現‘震驚’字眼 if channel.name == 'politics' and article.title.find('震驚') >= 0: return False # 娛樂頻道不允許a,b這兩個作者的文章 elif channel.name == 'entertainment' and article.author in ['a','b']: return False # some elif here... # 將文章與頻道的綁定關系寫進數據庫 return bind_relation(article, channel)
上面的函數確實能夠實現我們想要的功能了,但是存在一個顯而易見的問題:如果我們每增加一條準入規則,就需要改動一次distribute函數,這樣頻繁地對一個函數動刀顯然不是一個好的做法。
我們希望這個函數是一個更抽象的公共函數,他不需要被過多的改動,于是我們做一點改進,變成下面的函數:
def distribute(article, channel): # 文章準入判斷 can_push = channel.check(article) # 將文章與頻道的綁定關系寫進數據庫 if can_push: return bind_relation(article, channel) return False
將校驗頻道準入規則的這個功能用頻道類自己實現的check方法封裝起來,這樣每當有一個新的頻道需要創建,或者舊頻道需要更改校驗規則,則只需要負責維護各自頻道類的check方法就好了。
而distribute函數作為一個更高層級的存在則不會被影響到。
class Article: def __init__(self, title, author): self.title = title self.author = authorclass EntertainmentChannel: def __init__(self) self.name = 'entertainment' def check(article): if article.author in ['a','b']: return False return Trueclass PoliticsChannel: def __init__(self) self.name = 'politics' def check(article): if article.title.find('震驚') >= 0: return False return Truearitcle_a = Article('震驚!大笑1小時壽命減少60分鐘!', 'a')aritcle_b = Article('戰勝恐懼最好的辦法?', 'b')politics_channel = PoliticsChannel()entertainment_channel = EntertainmentChannel()distribute(aritcle_a, politics_channel) # Fasledistribute(aritcle_b, entertainment_channel) # Fasle
多態
上面對于distribute函數的改造結果,其實很類似于面向對象三大特征之一 —— 多態的應用。
簡單解釋起來, 多態就是同一操作(方法)被作用于不同的對象時,可以有不同的解釋,產生不同的執行結果。
例如上面的check方法,當它由EntertainmentChannel類實例調用時,檢查的是文章標題不能包含“震驚”字眼;由PoliticsChannel類實例調用時,檢查的是文章作者不能是’a'和‘b’。
多態在靜態語言如 Java 中,通常通過子類繼承父類,然后子類重寫父類中的某些方法來實現 多態。但是在python中,不需要搞子承父業這一套,只需要在不同的類里面實現好名字相同的方法,即可在運行時表現出 多態。
只不過,這種特征在python中一般不叫 多態,而是我們前面提到的—— 鴨子類型。
鴨子類型
鴨子類型的名字來源和具體應用場景前面已經描述過了,而關于鴨子類型的定義,網上出現最多的就是對文章開頭那句英文詩句的翻譯:
如果一只鳥走起來像鴨子,發出的聲音像鴨子,游起來像鴨子,那么它就是一只鴨子
這句話重點在于引導我們只關注事物的行為,而不是關注事物本身和它的表現。再看一個幫助理解的栗子:
class Duck: def sound(self): print('quack') def walk(self): print('da da da')class Dog: def sound(self): print('wang') def walk(self): print('tita tita tita')def walk_and_sound(animal): animal.walk() animal.sound()dog = Dog()duck = Duck()walk_and_sound(dog) # tita tita tita wangwalk_and_sound(duck) # da da da quack
就好像一只狗會走,會叫;鴨子也會走,會叫。狗有很多行為都跟鴨子相似,他們做的動作是一樣的,只是表現出來不一樣。
我們關注的是類有什么方法,能做什么,而不是類是怎么定義的,表現出來是怎么樣的。這個正是 鴨子類型想表達的思想。
鴨子類型的思想
總結
后記&引用
其實我仔細想了想,如果我早出生幾個世紀,在詹姆斯·惠特科姆·萊利寫出那句詩之前,喊出 「如果一個四肢動物走起來像狗,叫起來像狗,傻起來像狗,那它就是一只狗」~這樣一句話。
是不是現在就不叫 鴨子類型而改叫 狗子類型呢?唉,又錯過了名留千史的機會,還是應了一句老話:出名要趁早??!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。