我需要制作一个套接字模块的副本才能使用它,并为另一个套接字模块进行猴子修补并以不同的方式使用它。
这可能吗?
我的意思是真正复制一个模块,即在运行时获得与复制相同的结果socketmodule.c
,将initsocket()
函数更改为initmy_socket()
,并将其作为my_socket
扩展安装。
我需要制作一个套接字模块的副本才能使用它,并为另一个套接字模块进行猴子修补并以不同的方式使用它。
这可能吗?
我的意思是真正复制一个模块,即在运行时获得与复制相同的结果socketmodule.c
,将initsocket()
函数更改为initmy_socket()
,并将其作为my_socket
扩展安装。
你总是可以做一些技巧,比如导入一个模块然后从 sys.modules 中删除它或尝试复制一个模块。然而,Python 已经在它的标准库中提供了你想要的东西。
import imp # Standard module to do such things you want to.
# We can import any module including standard ones:
os1=imp.load_module('os1', *imp.find_module('os'))
# Here is another one:
os2=imp.load_module('os2', *imp.find_module('os'))
# This returns True:
id(os1)!=id(os2)
Python3.3+
imp.load_module
在 python3.3+ 中已弃用,建议使用importlib
#!/usr/bin/env python3 import sys import importlib.util SPEC_OS = importlib.util.find_spec('os') os1 = importlib.util.module_from_spec(SPEC_OS) SPEC_OS.loader.exec_module(os1) sys.modules['os1'] = os1 os2 = importlib.util.module_from_spec(SPEC_OS) SPEC_OS.loader.exec_module(os2) sys.modules['os2'] = os2 del SPEC_OS assert os1 is not os2, \ "Module `os` instancing failed"
在这里,我们两次导入相同的模块,但作为完全不同的模块对象。如果您检查 sys.modules,您可以看到您输入的两个名称作为 load_module 调用的第一个参数。查看文档以了解详细信息。
更新:
为了使这种方法的主要区别显而易见,我想让这一点更清楚:当您以这种方式导入相同的模块时,您将在运行时导入的每个其他模块都可以全局访问这两个版本,这正是提问者所需要的我明白了。
下面是另一个例子来强调这一点。
这两个语句的作用完全相同:
import my_socket_module as socket_imported
socket_imported = imp.load_module('my_socket_module',
*imp.find_module('my_socket_module')
)
在第二行,我们重复 'my_socket_module' 字符串两次,这就是 import 语句的工作方式;但实际上,这两个字符串的使用有两个不同的原因。
当我们将它传递给 find_module 时,第二次出现用作将在系统上找到的文件名。当我们将字符串传递给 load_module 方法时,第一次出现的字符串用作已加载模块的系统范围标识符。
因此,我们可以为它们使用不同的名称,这意味着我们可以让它像我们复制模块的 python 源文件并加载它一样工作。
socket = imp.load_module('socket_original', *imp.find_module('my_socket_module'))
socket_monkey = imp.load_module('socket_patched',*imp.find_module('my_socket_module'))
def alternative_implementation(blah, blah):
return 'Happiness'
socket_monkey.original_function = alternative_implementation
import my_sub_module
然后在 my_sub_module 中,我可以导入系统上不存在的“socket_patched”!我们在 my_sub_module.py 中。
import socket_patched
socket_patched.original_function('foo', 'bar')
# This call brings us 'Happiness'
这很恶心,但这可能就足够了:
import sys
# if socket was already imported, get rid of it and save a copy
save = sys.modules.pop('socket', None)
# import socket again (it's not in sys.modules, so it will be reimported)
import socket as mysock
if save is None:
# if we didn't have a saved copy, remove my version of 'socket'
del sys.modules['socket']
else:
# if we did have a saved copy overwrite my socket with the original
sys.modules['socket'] = save
下面是一些使用旧模块的函数和变量创建新模块的代码:
def copymodule(old):
new = type(old)(old.__name__, old.__doc__)
new.__dict__.update(old.__dict__)
return new
请注意,这对模块进行了相当浅的复制。字典是新创建的,所以基本的猴子补丁可以工作,但原始模块中的任何可变变量都将在两者之间共享。
编辑:根据评论,需要深拷贝。我尝试对模块进行猴子修补copy
以支持模块的深层副本,但这没有用。接下来我尝试导入模块两次,但由于模块缓存在 中sys.modules
,这给了我两次相同的模块。最后,我遇到的解决方案是sys.modules
在第一次导入后删除模块,然后再次导入。
from imp import find_module, load_module
from sys import modules
def loadtwice(name, path=None):
"""Import two copies of a module.
The name and path arguments are as for `find_module` in the `imp` module.
Note that future imports of the module will return the same object as
the second of the two returned by this function.
"""
startingmods = modules.copy()
foundmod = find_module(name, path)
mod1 = load_module(name, *foundmod)
newmods = set(modules) - set(startingmods)
for m in newmods:
del modules[m]
mod2 = load_module(name, *foundmod)
return mod1, mod2
将套接字模块物理复制到 socket_monkey 并从那里开始?我觉得你不需要任何“聪明”的解决方法......但我可能会过度简化!