monkey_patch,这个词多少年前就在python编程中了解过,但是一直没有系统的总结下,近日又再次遇到这个概念,故此记录一下。
在python中由于其内部的实现机制导致其具备较大的编程灵活性,也正是如此,monkey_patch在python中更加有应用场景。monkey_patch这个概念就是在python程序运行过程中对其原有的某些函数、类或模块进行替换,monkey_patch往往出现在程序启动最初的地方。比较形象的一个例子
可以说在原有项目不进行细节改动的情况下我们通过使用monkey_patch可以实现对原有项目中的功能函数替换的目的,这也是为啥叫patch的原因。
给出一个自己的toy code:
main.py
"""
import sub
import monkey_patch
monkey_patch.mk()
sub.sub_fun()
"""
import monkey_patch
monkey_patch.mk()
from sub import sub_fun
sub_fun()
sub.py
def sub_fun():
print("this is sub module")
monkey_patch.py
import sub
from sub import sub_fun
def sub_fun2():
print("this is monkey_patch module")
def mk():
sub.sub_fun = sub_fun2
sub.sub_fun.__name__ = "monkey_patch_sub_fun"
sub_fun = sub_fun2
sub_fun.__name__ = "monkey_patch_sub_fun"
需要注意的是这个monkey_patch对于import xx_module的情况还是比较好处理的,比较不好处理的就是那种from xx_module import xx
对于这种from xx_module import xx 的情况,我们就需要在monkey_patch中同样编写from xx_module import xx 并且在主文件启动时把这个monkey_patch放在最前面,否则就会失效。
from xx_module import xx 操作其实等价于import xx_module;xx=xx_module.xx,可以说之所以这样的情况monkey_patch不好处理是因为monkey_patch中的函数执行是不能修改其他模块中的变量命名空间的,比如main.py中的变量的命名空间为__main__,monkey_patch.py中变量的命名空间为monkey_patch,如果在main.py中执行from xx_module import xx 操作,那么xx的考虑命名空间的全名为__main__.xx,而import xx_module 这种情况则不论在任何子模块中调用该import都会有相同的命名空间变量xx_module.xx ,因此这种情况才可以在monkey_patch中容易的进行替换。
之所以把monkey_patch.mk() 操作放在main.py最初始的地方可以使from xx_module import xx 有效是因为monkey_patch.py中已经执行了from xx_module import xx 这个操作并对此进行了替换,这样其他模块中再次执行from xx_module import xx 就不会有具体的操作,因为已经xx_module下的xx变量已经存在在全局的命名空间下。
总的来说, import xx_module 不论在启动模块还是子模块中执行操作后便在命名空间中留下了记录,以后不论任何模块通过xx_module.xx的方式来进行赋值或修改都是直接对全局唯一的变量进行操作的,并且所有模块都可以共享这个全局命名空间下的这个xx_module.xx变量。但是对于from xx_module import xx 操作来说,只有在第一次执行的模块内是可以对这个xx变量进行赋值或修改后其他模块也可以通过from xx_module import xx 方式读取该值的,但是其他模块即使通过该方式读取该值也不能对其修改后使其在所有子模块内被共享,因为此时的xx的命名框架是当前执行模块内的,换句话说如果使用from xx_module import xx 方式的话,只有在其第一次执行的模块内对其进行的操作是可以传递到其他模块的,而其他模块(不是第一次执行from xx_module import xx 操作的模块)对其的修改只在自身模块的命名框架下是有效的。
from xx_module import xx 方式调用xx,那么在任何子模块中执行后其命名空间都为其所执行模块的名称,也就是说如果是xyz.py模块调用那么其变量名可以视作xyz.xx,这也就是为什么只有第一次执行from xx_module import xx 方式调用的模块才可以对其进行修改。
import xx_module方式调用xx_module.xx,那么在所有模块中带命名空间的变量全名为xx_module.xx 。
对于整个模块进行monkey_patch替换则需要修改sys.modules中的内容
>>> import sys
>>> sys.modules['sys']
<module 'sys' (built-in)>
>>>
>>> sys.modules['os']=sys.modules['sub']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'sub'
>>> import sub
>>> sys.modules['os']=sys.modules['sub']
>>>
>>> os
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'os' is not defined
>>> import os
>>> os.sub_fun
参考:
https://blog.51cto.com/iceyao/1695266
https://zhuanlan.zhihu.com/p/37679547
标签:monkey,python,module,patch,xx,import,sub From: https://blog.51cto.com/u_15642578/6026153