什么才是【Python】中的鸭子类型和猴子补丁
Python 開發者可能都聽說過鴨子類型和猴子補丁這兩個詞,即使沒聽過,也大概率寫過相關的代碼,只不過并不了解其背后的技
術要點是這兩個詞而已。
我最近在工作的時候,也會問這兩個概念,很多人答的也并不是很好。但是當我向他們解釋完之后,普遍都會恍然大悟:
“哦,是這個啊,我用過”。
所以,我決定來寫一篇文章,探討一下這兩個技術。
鴨子類型
引用維基百科中的一段解釋:
鴨子類型(duck typing)在程序設計中是動態類型的一種風格。在這種風格中,一個對象有效的語義,不是由繼承自特定的類或
實現特定的接口,而是由"當前方法和屬性的集合"決定。
更通俗一點的說:
當看到一只鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那么這只鳥就可以被稱為鴨子。
也就是說,在鴨子類型中,關注點在于對象的行為,能作什么;而不是關注對象所屬的類型。
我們看一個例子,更形象地展示一下:
Python學習交流Q群:906715085#### # 這是一個鴨子(Duck)類 class Duck:def eat(self):print("A duck is eating...")def walk(self):print("A duck is walking...")# 這是一個狗(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 是一門動態語言,沒有嚴格的類型檢查。只要 Duck 和 Dog 分別實現了 eat 和 walk 方法就可以直接調用。
再比如 list.extend() 方法,除了 list 之外,dict 和 tuple 也可以調用,只要它是可迭代的就都可以調用。
看過上例之后,應該對「對象的行為」和「對象所屬的類型」有更深的體會了吧。
再擴展一點,其實鴨子類型和接口挺像的,只不過沒有顯式定義任何接口。
比如用 Go 語言來實現鴨子類型,代碼是這樣的:
package mainimport "fmt"// 定義接口,包含 Eat 方法 type Duck interface {Eat() }// 定義 Cat 結構體,并實現 Eat 方法 type Cat struct{}func (c *Cat) Eat() {fmt.Println("cat eat") }// 定義 Dog 結構體,并實現 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()} }通過顯式定義一個 Duck 接口,每個結構體實現接口中的方法來實現。
猴子補丁
猴子補丁(Monkey Patch)的名聲不太好,因為它會在運行時動態修改模塊、類或函數,通常是添加功能或修正缺陷。
猴子補丁在內存中發揮作用,不會修改源碼,因此只對當前運行的程序實例有效。
但如果濫用的話,會導致系統難以理解和維護。
主要有兩個問題:
1.補丁會破壞封裝,通常與目標緊密耦合,因此很脆弱
2.打了補丁的兩個庫可能相互牽絆,因為第二個庫可能會撤銷第一個庫的補丁
所以,它被視為臨時的變通方案,不是集成代碼的推薦方式。
按照慣例,還是舉個例子來說明:
# 定義一個Dog類 class Dog:def eat(self):print("A dog is eating ...")# 在類的外部給 Dog 類添加猴子補丁 def walk(self):print("A dog is walking ...")Dog.walk = walk# 調用方式與類的內部定義的屬性和方法一樣 dog = Dog() dog.eat() dog.walk() 程序輸出: A dog is eating ... A dog is walking ...這里相當于在類的外部給 Dog 類增加了一個 walk 方法,而調用方式與類的內部定義的屬性和方法一樣。
再舉一個比較實用的例子,比如我們常用的 json 標準庫,如果說想用性能更高的 ujson 代替的話,那勢必需要將每個文件的引入:
import json改成:
import ujson as json如果這樣改起來成本就比較高了。這個時候就可以考慮使用猴子補丁,只需要在程序入口加上:
import json import ujson def monkey_patch_json(): json.__name__ = 'ujson' json.dumps = ujson.dumps json.loads = ujson.loads monkey_patch_json()這樣在以后調用 dumps 和 loads 方法的時候就是調用的 ujson 包,還是很方便的。但猴子補丁就是一把雙刃劍,看需,謹慎使用吧。最后,人生沒有不散的宴席,今天的這篇文章到這里就要跟大家說拜拜了,下一篇見啦。
總結
以上是生活随笔為你收集整理的什么才是【Python】中的鸭子类型和猴子补丁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 机器学习(8)——回归和异常值处理(安然
- 下一篇: 数据结构练习题——线性表