1. 什么是猴子補(bǔ)丁
Python是一種典型的動(dòng)態(tài)腳本語言。它不僅具有 動(dòng)態(tài)類型(dynamic type) ,而且它的 對(duì)象模型(object model) 也是動(dòng)態(tài)的。Python的類是可變的(mutable),方法(methods)只是類的屬性(attributes);這允許我們?cè)?運(yùn)行時(shí)(run time) 修改其行為。這被稱為猴子補(bǔ)丁(Monkey Patching), 它指的是偷偷地更改代碼。
Monkey Patching只是在 運(yùn)行時(shí)(run time) 動(dòng)態(tài)替換屬性(attributes)。 而在Python中,術(shù)語monkey patch指的是對(duì)函數(shù)(function)、類(class)或模塊(module)的動(dòng)態(tài)(或運(yùn)行時(shí))修改。
2. 舉例說明
假設(shè)在monkey.py文件中已經(jīng)定義了一個(gè)類:
# monkey.py
class Me:
def who_am_i(self):
print("I am a Monkey")
假設(shè)monkey.py文件中的Me這個(gè)類不是我寫的,我只是用到了這個(gè)類 為了演示的方便,這個(gè)類只有一個(gè)who_am_i() 方法,作用是打印"I am a Monkey"
現(xiàn)在我在另外一個(gè)文件中想要調(diào)用這個(gè)類,但是發(fā)現(xiàn)這個(gè)類里面的who_am_i() 方法不是我想要的內(nèi)容。
由于我是一個(gè)人類,我不喜歡打印我是一個(gè)猴子,我想要打印 “I am human”,
所以我給猴子對(duì)象打補(bǔ)?。ㄟ@里是一個(gè)雙關(guān)語,就是monkey patch的名字的來源),我們可以這么實(shí)現(xiàn):
import monkey # 導(dǎo)入用到的別人寫的monkey模塊
?
def i_am_human(self): # 定義一個(gè)我們想要的方法
print("I am human")
?
print(f"{monkey.Me.who_am_i = }") # 替換前,將原來的方法地址打印出來
monkey.Me.who_am_i = i_am_human # 將"who_am_i"的地址替換為"i_am_human"
print(f"{monkey.Me.who_am_i = }") # 替換后,將原來的方法地址打印出來
?
obj = monkey.Me() # 實(shí)例化一個(gè)對(duì)象
?
print(f"{hasattr(obj, 'i_am_human') = }")
print(f"{hasattr(obj, 'who_am_i') = }")
obj.who_am_i() # 直接調(diào)用 "who_am_i" 而不是 "i_am_human()"
輸出的結(jié)果:
monkey.Me.who_am_i = <function Me.who_am_i at 0x7ff6ab1d9af0>
monkey.Me.who_am_i = <function i_am_human at 0x7ff6ab2a0310>
hasattr(obj, 'i_am_human') = False
hasattr(obj, 'who_am_i') = True
I am human
這個(gè)例子的結(jié)論:
1.我們可以自己定義一個(gè)新的方法(或者函數(shù))來更改掉原來類的方法
2.替換以后,原來類的方法名稱還在,但是它的內(nèi)存地址已經(jīng)發(fā)生變化了
3.調(diào)用的時(shí)候,只能使用原來的方法名來調(diào)用,而不是新的方法名稱
4.新的方法名稱只是包含了實(shí)現(xiàn)過程,對(duì)于類本身,是看不到這個(gè)方法名稱的。
3. 其他對(duì)象使用猴子補(bǔ)丁
3.1. 使用猴子補(bǔ)丁修復(fù)類的實(shí)例
上面使用了猴子補(bǔ)丁來修復(fù)了一個(gè)類的方法, 那么該類的所有實(shí)例使用該方法的時(shí)候都將使用的是修補(bǔ)后的方法。
如果我們想要減少影響,只修補(bǔ)特定的實(shí)例對(duì)象, 可是可以完成的,代碼如下:
import types
import monkey # 導(dǎo)入用到的別人寫的monkey模塊
?
monkey1 = monkey.Me()
monkey2 = monkey.Me()
?
def i_am_human(self):
print("I am human")
?
monkey2.who_am_i = types.MethodType(i_am_human, monkey2)
monkey1.who_am_i()
monkey2.who_am_i()
輸出的結(jié)果:
I am a Monkey
I am human
這個(gè)例子的結(jié)論:
同一個(gè)類的兩個(gè)實(shí)例中,我們可以單獨(dú)給某一個(gè)實(shí)例打猴子補(bǔ)丁,而完全不影響另外一個(gè)實(shí)例.
3.2. 其他對(duì)象使用猴子補(bǔ)丁
我們還可以對(duì)其他的對(duì)象使用猴子補(bǔ)丁,比如模塊等, 這里有一個(gè)比較實(shí)用的例子:
比如你的一個(gè)項(xiàng)目中,很多python文件中都用到了import json,后來發(fā)現(xiàn)如果使用ujson性能會(huì)更高, 但是覺得把每個(gè)文件的 import json 都改成 import ujson as json 成本較高(不要光想著替換,很多項(xiàng)目不僅僅有你一個(gè)開發(fā)人員); 或者僅僅想測(cè)試一下用ujson替換json是否符合預(yù)期。
對(duì)于這種需求,只需要在程序的主入口處加上下面的代碼:
import json
import ujson
?
def monkey_patch_json():
json.__name__ = 'ujson'
json.dumps = ujson.dumps
json.loads = ujson.loads
?
monkey_patch_json()
這樣后面:
- 所有用到j(luò)son.dumps就會(huì)自動(dòng)調(diào)用ujson.dumps
- 所有用到j(luò)son.loads就會(huì)自動(dòng)調(diào)用ujson.loads
4. 使用場(chǎng)景與注意事情
可以看到猴子修補(bǔ)非常強(qiáng)大,幾乎可以在任何地方修改原來類的實(shí)現(xiàn)或者對(duì)象的原有功能。但是恰恰是由于其可以隨時(shí)隨地修改,會(huì)造成某個(gè)對(duì)象的具體功能是在哪兒實(shí)現(xiàn)的這點(diǎn)非常不明確(破環(huán)封裝),這會(huì)嚴(yán)重影響程序的魯棒性,容易引發(fā)不必要的Bug。 所以要慎用!
猴子補(bǔ)丁合適的使用場(chǎng)景:文章來源:http://www.zghlxwxcb.cn/news/detail-781115.html
- 我們正在處理來自其他人的寫的公共代碼,優(yōu)化了一個(gè)小的實(shí)現(xiàn),我們目前不想對(duì)其源碼進(jìn)行修改(因?yàn)槠渌诉€有可能在用這些代碼,或者其他版本中有可能用到),我可以將這個(gè)補(bǔ)丁放在自己的代碼中,即保證了功能的實(shí)現(xiàn),也不影響別人實(shí)現(xiàn)
- 我們正在處理來自其他人的遺留代碼或代碼,我們不想對(duì)其進(jìn)行廣泛修改,但仍然希望使其與不同版本的庫或環(huán)境一起運(yùn)行,這非常有用。
因此,對(duì)于猴子補(bǔ)丁,我個(gè)人建議:文章來源地址http://www.zghlxwxcb.cn/news/detail-781115.html
- 如果代碼的影響范圍可控,不要使用猴子補(bǔ)丁,直接更改原來方法的實(shí)現(xiàn)
- 如果要使用猴子補(bǔ)丁,盡量在最終端的類或者實(shí)例中,不要在中間類中使用
到了這里,關(guān)于python的猴子補(bǔ)丁(Monkey Patching)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!