如何在导入模块前进行猴子补丁以覆盖其__init__.py内容?
如何在导入第三方库时提前猴子补丁阻止其破坏日志配置
我在用第三方库floris,它的floris/__init__.py里包含如下代码:
from . import logging_manager logging_manager._setup_logger()
这段代码会破坏我精心配置的日志设置,比如向根日志器添加自定义格式器。我自己的代码里是这么导入并使用的:
from floris.simulation.turbine import Turbine def run(): do_stuff_with_floris = Turbine()
提交上游修复耗时太长,想对floris做猴子补丁,但问题是要补丁的函数在模块导入时就会被调用,试了下面的代码无法实现:
# 我无法这样做 class MonkeyLoggingManager: def _setup_logging(self): pass floris.logging_manager = MonkeyLoggingManager() # ^^^^ 因为此时floris尚未导入。 from floris.simulation.turbine import Turbine
请问是否可以在导入模块之前或导入时对其进行猴子补丁?
解决方案:手动控制导入流程实现提前补丁
可以通过Python的模块导入机制,在floris初始化代码执行前完成补丁,有两种可行方式:
方式一:用importlib手动加载并补丁模块
- 先定位并加载
floris.logging_manager模块,但不触发其顶层代码执行:
import importlib.util import sys # 找到floris.logging_manager模块的规格信息 spec = importlib.util.find_spec("floris.logging_manager") # 创建模块对象 logging_manager_module = importlib.util.module_from_spec(spec) # 将模块注册到sys.modules,让后续导入复用这个对象 sys.modules["floris.logging_manager"] = logging_manager_module
- 替换模块中的
_setup_logger为空实现:
def dummy_setup_logger(): pass logging_manager_module._setup_logger = dummy_setup_logger
- 正常导入需要的模块:
from floris.simulation.turbine import Turbine def run(): do_stuff_with_floris = Turbine()
方式二:用sys.modules提前注册伪造模块
这是更简洁的写法,直接在sys.modules里塞一个伪造的floris.logging_manager模块:
import sys # 创建一个伪造的模块对象 fake_logging_manager = type(sys)("floris.logging_manager") # 给伪造模块添加空实现的_setup_logger函数 fake_logging_manager._setup_logger = lambda: None # 注册到sys.modules sys.modules["floris.logging_manager"] = fake_logging_manager # 现在正常导入即可,floris/__init__.py会调用我们的空函数 from floris.simulation.turbine import Turbine def run(): do_stuff_with_floris = Turbine()
原理说明
Python导入模块时,会优先检查sys.modules字典里是否已经存在目标模块。我们提前把伪造/预加载的floris.logging_manager模块注册进去,当floris/__init__.py执行from . import logging_manager时,就会直接复用我们的模块,调用_setup_logger时只会执行我们定义的空实现,不会再破坏原有日志配置。
内容的提问来源于stack exchange,提问作者thclark
相关产品推荐
相关产品推荐

