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