如何在运行时制作python模块的副本?

Dmi*_*mov 31 python python-c-extension

我需要制作一个套接字模块的副本才能使用它,并且需要另外一个套接字模块进行猴子修补并以不同的方式使用它.

这可能吗?

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

has*_*sin 26

您总是可以执行一些操作,例如导入模块,然后从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)
Run Code Online (Sandbox Code Playgroud)

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"
Run Code Online (Sandbox Code Playgroud)

在这里,我们导入相同的模块两次,但作为完全不同的模块对象.如果检查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')
)
Run Code Online (Sandbox Code Playgroud)

在第二行,我们重复'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
Run Code Online (Sandbox Code Playgroud)

然后在my_sub_module中,我可以导入系统上不存在的'socket_patched'!这里我们在my_sub_module.py中.

import socket_patched
socket_patched.original_function('foo', 'bar')
# This call brings us 'Happiness'
Run Code Online (Sandbox Code Playgroud)

  • 真棒!这帮助我解决了用于单元测试的模拟修补模块方法的问题,同时还为实际操作并行调用这些模块.这两个偶尔会发生冲突.有了这个,我能够克隆模块进行测试并避免冲突! (2认同)

Nat*_*ert 11

这非常恶心,但这可能就足够了:

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
Run Code Online (Sandbox Code Playgroud)


Abe*_*lus 6

这里有一些代码用于创建一个包含旧函数和变量的新模块:

def copymodule(old):
    new = type(old)(old.__name__, old.__doc__)
    new.__dict__.update(old.__dict__)
    return new
Run Code Online (Sandbox Code Playgroud)

请注意,这是一个相当浅的模块副本.字典是新创建的,因此基本的猴子修补将起作用,但原始模块中的任何可变项将在两者之间共享.

编辑:根据评论,需要深层复制.我试着用猴子修补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
Run Code Online (Sandbox Code Playgroud)