原文鏈接: Python 中的鴨子類型和猴子補(bǔ)丁
10年積累的成都做網(wǎng)站、成都網(wǎng)站設(shè)計(jì)經(jīng)驗(yàn),可以快速應(yīng)對(duì)客戶對(duì)網(wǎng)站的新想法和需求。提供各種問題對(duì)應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識(shí)你,你也不認(rèn)識(shí)我。但先網(wǎng)站制作后付款的網(wǎng)站建設(shè)流程,更有濱江免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
大家好,我是老王。
Python 開發(fā)者可能都聽說過鴨子類型和猴子補(bǔ)丁這兩個(gè)詞,即使沒聽過,也大概率寫過相關(guān)的代碼,只不過并不了解其背后的技術(shù)要點(diǎn)是這兩個(gè)詞而已。
我最近在面試候選人的時(shí)候,也會(huì)問這兩個(gè)概念,很多人答的也并不是很好。但是當(dāng)我向他們解釋完之后,普遍都會(huì)恍然大悟:“哦,是這個(gè)啊,我用過”。
所以,我決定來寫一篇文章,探討一下這兩個(gè)技術(shù)。
引用維基百科中的一段解釋:
鴨子類型(duck typing)在程序設(shè)計(jì)中是動(dòng)態(tài)類型的一種風(fēng)格。在這種風(fēng)格中,一個(gè)對(duì)象有效的語(yǔ)義,不是由繼承自特定的類或?qū)崿F(xiàn)特定的接口,而是由"當(dāng)前方法和屬性的集合"決定。
更通俗一點(diǎn)的說:
當(dāng)看到一只鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那么這只鳥就可以被稱為鴨子。
也就是說,在鴨子類型中,關(guān)注點(diǎn)在于對(duì)象的行為,能作什么;而不是關(guān)注對(duì)象所屬的類型。
我們看一個(gè)例子,更形象地展示一下:
# 這是一個(gè)鴨子(Duck)類
class Duck:
def eat(self):
print("A duck is eating...")
def walk(self):
print("A duck is walking...")
# 這是一個(gè)狗(Dog)類
class Dog:
def eat(self):
print("A dog is eating...")
def walk(self):
print("A dog is walking...")
def animal(obj):
obj.eat()
obj.walk()
if __name__ == '__main__':
animal(Duck())
animal(Dog())
程序輸出:
A duck is eating...
A duck is walking...
A dog is eating...
A dog is walking...
Python 是一門動(dòng)態(tài)語(yǔ)言,沒有嚴(yán)格的類型檢查。只要 Duck
和 Dog
分別實(shí)現(xiàn)了 eat
和 walk
方法就可以直接調(diào)用。
再比如 list.extend()
方法,除了 list
之外,dict
和 tuple
也可以調(diào)用,只要它是可迭代的就都可以調(diào)用。
看過上例之后,應(yīng)該對(duì)「對(duì)象的行為」和「對(duì)象所屬的類型」有更深的體會(huì)了吧。
再擴(kuò)展一點(diǎn),其實(shí)鴨子類型和接口挺像的,只不過沒有顯式定義任何接口。
比如用 Go 語(yǔ)言來實(shí)現(xiàn)鴨子類型,代碼是這樣的:
package main
import "fmt"
// 定義接口,包含 Eat 方法
type Duck interface {
Eat()
}
// 定義 Cat 結(jié)構(gòu)體,并實(shí)現(xiàn) Eat 方法
type Cat struct{}
func (c *Cat) Eat() {
fmt.Println("cat eat")
}
// 定義 Dog 結(jié)構(gòu)體,并實(shí)現(xiàn) Eat 方法
type Dog struct{}
func (d *Dog) Eat() {
fmt.Println("dog eat")
}
func main() {
var c Duck = &Cat{}
c.Eat()
var d Duck = &Dog{}
d.Eat()
s := []Duck{
&Cat{},
&Dog{},
}
for _, n := range s {
n.Eat()
}
}
通過顯式定義一個(gè) Duck
接口,每個(gè)結(jié)構(gòu)體實(shí)現(xiàn)接口中的方法來實(shí)現(xiàn)。
猴子補(bǔ)丁(Monkey Patch)的名聲不太好,因?yàn)樗鼤?huì)在運(yùn)行時(shí)動(dòng)態(tài)修改模塊、類或函數(shù),通常是添加功能或修正缺陷。
猴子補(bǔ)丁在內(nèi)存中發(fā)揮作用,不會(huì)修改源碼,因此只對(duì)當(dāng)前運(yùn)行的程序?qū)嵗行А?/p>
但如果濫用的話,會(huì)導(dǎo)致系統(tǒng)難以理解和維護(hù)。
主要有兩個(gè)問題:
所以,它被視為臨時(shí)的變通方案,不是集成代碼的推薦方式。
按照慣例,還是舉個(gè)例子來說明:
# 定義一個(gè)Dog類
class Dog:
def eat(self):
print("A dog is eating ...")
# 在類的外部給 Dog 類添加猴子補(bǔ)丁
def walk(self):
print("A dog is walking ...")
Dog.walk = walk
# 調(diào)用方式與類的內(nèi)部定義的屬性和方法一樣
dog = Dog()
dog.eat()
dog.walk()
程序輸出:
A dog is eating ...
A dog is walking ...
這里相當(dāng)于在類的外部給 Dog
類增加了一個(gè) walk
方法,而調(diào)用方式與類的內(nèi)部定義的屬性和方法一樣。
再舉一個(gè)比較實(shí)用的例子,比如我們常用的 json
標(biāo)準(zhǔn)庫(kù),如果說想用性能更高的 ujson
代替的話,那勢(shì)必需要將每個(gè)文件的引入:
import json
改成:
import ujson as json
如果這樣改起來成本就比較高了。這個(gè)時(shí)候就可以考慮使用猴子補(bǔ)丁,只需要在程序入口加上:
import json
import ujson
def monkey_patch_json():
json.__name__ = 'ujson'
json.dumps = ujson.dumps
json.loads = ujson.loads
monkey_patch_json()
這樣在以后調(diào)用 dumps
和 loads
方法的時(shí)候就是調(diào)用的 ujson
包,還是很方便的。
但猴子補(bǔ)丁就是一把雙刃劍,問題也在上文中提到了,看需,謹(jǐn)慎使用吧。
以上就是本文的全部?jī)?nèi)容,如果覺得還不錯(cuò)的話,歡迎點(diǎn)贊,轉(zhuǎn)發(fā)和關(guān)注,感謝支持。
推薦閱讀: