Python monkey patching不生效,如何正确实现get_datetime函数的猴子补丁?
补丁失效的核心原因
1. 模块全局代码执行时机早于补丁执行
Python模块在首次被导入时,会一次性执行文件内所有全局作用域的代码。你在generation.py里的NAME是全局变量,定义时直接调用了get_datetime(),也就是说只要bobs.generation模块被导入过,get_datetime()就已经执行完成了,你后续打补丁根本不会影响已经计算完成的NAME值。
即使你补丁打对了,也只能影响补丁执行之后新的get_datetime()调用,修改不了之前已经算好的全局变量。
2. 补丁内容类型错误
你当前的补丁是直接把get_datetime这个函数引用替换成了一个固定的datetime对象:
monkey_patch.setattr( "bobs.generation.get_datetime", datetime.datetime(2021, 11, 18), )
而原逻辑是调用get_datetime()(函数调用)获取时间,你替换成固定对象后如果调用get_datetime()会直接抛出「datetime对象不可调用」的错误,你现在没报错还能拿到now()的结果,说明你的补丁执行的时候,bobs.generation模块早就已经导入完成,甚至get_datetime的引用都已经被用完了,补丁根本没机会生效。
3. 导入后修改源模块引用不生效
from utils.dtutils import get_datetime这个导入操作,会把dtutils里的get_datetime函数引用复制一份到generation模块的命名空间里。如果你的补丁是在generation导入完成后才修改bobs.utils.dtutils.get_datetime,只会影响之后新的导入,不会修改已经存在于generation模块里的独立引用。
修复方案
方案1:调整补丁执行时机,提前到模块导入前
保证你的猴子补丁代码执行在任何导入bobs.generation的代码之前,同时补丁要替换成返回固定时间的函数,而不是直接放时间对象:
# 先打补丁,再导入模块 import datetime from monkey_patch import setattr def mock_get_datetime(): return datetime.datetime(2021, 11, 18) setattr("bobs.utils.dtutils.get_datetime", mock_get_datetime) # 补丁打完再导入generation模块,此时全局NAME计算用的就是mock的函数 from bobs.generation import NAME
方案2:调整业务代码逻辑,避免全局作用域提前执行调用
如果没法控制导入顺序,就把全局NAME的计算改成懒加载的形式:
# generation.py 修改后 from utils.dtutils import get_datetime _NAME = None def get_name(): global _NAME if _NAME is None: _NAME = f"""{KMTER}/{get_datetime().strftime('%Y-%m-%d')}/dadsdge.fs""" return _NAME
这样调用get_name()的时候才会执行get_datetime(),只要补丁打在第一次调用get_name()之前就可以生效。
内容的提问来源于stack exchange,提问作者caasswa

