如何在运行时复制python模块?

如何在运行时复制python模块?,python,python-c-extension,Python,Python C Extension,我需要制作一个套接字模块的副本,以便能够使用它,并对多个套接字模块进行修补,以不同的方式使用它 这可能吗 我的意思是真正复制一个模块,即在运行时获得相同的结果,就像我复制了socketmodule.c,将initsocket()函数更改为initmy_socket(),并将其安装为my_socket扩展。将socket模块物理复制到socket_monkey并从那里开始?我觉得你不需要任何“聪明”的工作。。。但我可能过于简化了 以下是一些代码,它们使用旧模块的函数和变量创建了一个新模块: def

我需要制作一个套接字模块的副本,以便能够使用它,并对多个套接字模块进行修补,以不同的方式使用它

这可能吗


我的意思是真正复制一个模块,即在运行时获得相同的结果,就像我复制了
socketmodule.c
,将
initsocket()
函数更改为
initmy_socket()
,并将其安装为my_socket扩展。

将socket模块物理复制到socket_monkey并从那里开始?我觉得你不需要任何“聪明”的工作。。。但我可能过于简化了

以下是一些代码,它们使用旧模块的函数和变量创建了一个新模块:

def copymodule(old):
    new = type(old)(old.__name__, old.__doc__)
    new.__dict__.update(old.__dict__)
    return new
请注意,这是一个相当浅的模块副本。字典是新创建的,因此基本的monkey补丁可以工作,但是原始模块中的任何可变项都将在两者之间共享

编辑:根据评论,需要深度复制。我试着用monkey修补
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

这很恶心,但这可能足够了:

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

您始终可以执行一些技巧,例如导入模块,然后从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)
蟒蛇3.3+
imp.load_模块
is,建议使用
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,则可以看到作为加载模块调用的第一个参数输入的两个名称。详细信息,请参阅

更新:

为了使这种方法的主要区别显而易见,我想让这一点更清楚:当您以这种方式导入同一个模块时,您在运行时导入的每个其他模块都将具有全局可访问的两个版本,这正是提问者所需要的,正如我所理解的

下面是强调这一点的另一个例子

这两种说法的作用完全相同:

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
然后在我的子模块中,我可以导入系统上不存在的“socket\u patched”!这里是my_sub_module.py

import socket_patched
socket_patched.original_function('foo', 'bar')
# This call brings us 'Happiness'

您不能简单地执行以下操作:1)导入基本模块2)猴子补丁3)使用补丁模块4)当您使用补丁模块调用重载(套接字)时?我需要同时使用它们。@Dmitry:为什么您认为需要猴子补丁?在大多数情况下,这不是最好的方法。你不能使用一个包装器模块来满足你的需求吗?我要澄清一下:我正在为一个使用mankey补丁套接字的python代码编写一个跟踪程序。跟踪器也需要使用套接字模块,但猴子补丁会破坏一切。所以我需要一份跟踪程序中的套接字副本。同样,tracer是C扩展,它可以使用Python中的套接字的C实现,但我不知道如何也做到这一点。修复使用monkey patched socket模块的代码(因为这显然是破坏性的设计)。改为在套接字周围使用猴子补丁包装。你说的“物理复制”是什么意思?使用cp或copy(取决于操作系统)复制文件我怀疑我会在这里结束复制粘贴。:)这不是很好,但解决了问题。我想在没有其他方法的情况下,我会尝试这样做……不幸的是,这也很复杂,因为socketmodule.c在Python2和Python3中有所不同。因此,这也不是一个解决方案。我同意这并不理想,但最实际的做法是得到东西。我需要一个模块的完整副本,其中包含可变项的副本,并将其设置为一个具有完整、深度副本的版本。太棒了!这帮助我解决了单元测试中模拟修补模块方法的问题,同时也并行调用这些模块进行实际操作。两人偶尔会发生冲突。有了这个,我可以克隆模块进行测试并避免冲突!这有一个副作用,因为它在导入时执行模块,而不是像问题中的状态那样在没有执行的情况下进行深度复制。