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

时间:2021-09-19 23:16:51

I need to make a copy of a socket module to be able to use it and to have one more socket module monkey-patched and use it differently.

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

Is this possible?

这可能吗?

I mean to really copy a module, namely to get the same result at runtime as if I've copied socketmodule.c, changed the initsocket() function to initmy_socket(), and installed it as my_socket extension.

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

4 个解决方案

#1


22  

You can always do tricks like importing a module then deleting it from sys.modules or trying to copy a module. However, Python already provides what you want in its Standard Library.

您可以随时执行导入模块,然后从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 is deprecated in python3.3+, and recommends the use of importlib

在python3.3 +中不推荐使用imp.load_module,并建议使用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"

Here, we import the same module twice but as completely different module objects. If you check sys.modules, you can see two names you entered as first parameters to load_module calls. Take a look at the documentation for details.

在这里,我们导入相同的模块两次,但作为完全不同的模块对象。如果检查sys.modules,则可以看到作为load_module调用的第一个参数输入的两个名称。有关详细信息,请查看文档。

UPDATE:

To make the main difference of this approach obvious, I want to make this clearer: When you import the same module this way, you will have both versions globally accessible for every other module you import in runtime, which is exactly what the questioner needs as I understood.

为了使这种方法的主要区别显而易见,我希望更清楚:当您以这种方式导入相同的模块时,您将在运行时导入的每个其他模块全局访问这两个版本,这正是提问者所需要的我明白了

Below is another example to emphasize this point.

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

These two statements do exactly the same thing:

这两个语句完全相同:

import my_socket_module as socket_imported

socket_imported = imp.load_module('my_socket_module',
    *imp.find_module('my_socket_module')
)

On second line, we repeat 'my_socket_module' string twice and that is how import statement works; but these two strings are, in fact, used for two different reasons.

在第二行,我们重复'my_socket_module'字符串两次,这就是import语句的工作原理;但事实上,这两个字符串有两个不同的原因。

Second occurrence as we passed it to find_module is used as the file name that will be found on the system. The first occurrence of the string as we passed it to load_module method is used as system-wide identifier of the loaded module.

我们将它传递给find_module时的第二次出现用作将在系统上找到的文件名。当我们将它传递给load_module方法时,第一次出现的字符串被用作加载模块的系统范围标识符。

So, we can use different names for these which means we can make it work exactly like we copied the python source file for the module and loaded it.

因此,我们可以为这些使用不同的名称,这意味着我们可以使它像我们复制模块的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

Then in my_sub_module, I can import 'socket_patched' which does not exist on system! Here we are in my_sub_module.py.

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

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

#2


11  

This is pretty disgusting, but this might suffice:

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

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

#3


4  

Here's some code that creates a new module with the functions and variables of the old:

这里有一些代码可以创建一个带有旧函数和变量的新模块:

def copymodule(old):
    new = type(old)(old.__name__, old.__doc__)
    new.__dict__.update(old.__dict__)
    return new

Note that this does a fairly shallow copy of the module. The dictionary is newly created, so basic monkey patching will work, but any mutables in the original module will be shared between the two.

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

Edit: According to the comment, a deep copy is needed. I tried messing around with monkey-patching the copy module to support deep copies of modules, but that didn't work. Next I tried importing the module twice, but since modules are cached in sys.modules, that gave me the same module twice. Finally, the solution I hit upon was removing the modules from sys.modules after importing it the first time, then importing it again.

编辑:根据评论,需要深层复制。我试着乱搞猴子修补副本模块以支持模块的深层副本,但这不起作用。接下来我尝试导入模块两次,但由于模块缓存在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

#4


1  

Physically copy the socket module to socket_monkey and go from there? I don't feel you need any "clever" work-around... but I might well be over simplifying!

物理上将套接字模块复制到socket_monkey并从那里开始?我觉得你不需要任何“聪明”的解决方法......但我可能会过度简化!

#1


22  

You can always do tricks like importing a module then deleting it from sys.modules or trying to copy a module. However, Python already provides what you want in its Standard Library.

您可以随时执行导入模块,然后从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 is deprecated in python3.3+, and recommends the use of importlib

在python3.3 +中不推荐使用imp.load_module,并建议使用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"

Here, we import the same module twice but as completely different module objects. If you check sys.modules, you can see two names you entered as first parameters to load_module calls. Take a look at the documentation for details.

在这里,我们导入相同的模块两次,但作为完全不同的模块对象。如果检查sys.modules,则可以看到作为load_module调用的第一个参数输入的两个名称。有关详细信息,请查看文档。

UPDATE:

To make the main difference of this approach obvious, I want to make this clearer: When you import the same module this way, you will have both versions globally accessible for every other module you import in runtime, which is exactly what the questioner needs as I understood.

为了使这种方法的主要区别显而易见,我希望更清楚:当您以这种方式导入相同的模块时,您将在运行时导入的每个其他模块全局访问这两个版本,这正是提问者所需要的我明白了

Below is another example to emphasize this point.

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

These two statements do exactly the same thing:

这两个语句完全相同:

import my_socket_module as socket_imported

socket_imported = imp.load_module('my_socket_module',
    *imp.find_module('my_socket_module')
)

On second line, we repeat 'my_socket_module' string twice and that is how import statement works; but these two strings are, in fact, used for two different reasons.

在第二行,我们重复'my_socket_module'字符串两次,这就是import语句的工作原理;但事实上,这两个字符串有两个不同的原因。

Second occurrence as we passed it to find_module is used as the file name that will be found on the system. The first occurrence of the string as we passed it to load_module method is used as system-wide identifier of the loaded module.

我们将它传递给find_module时的第二次出现用作将在系统上找到的文件名。当我们将它传递给load_module方法时,第一次出现的字符串被用作加载模块的系统范围标识符。

So, we can use different names for these which means we can make it work exactly like we copied the python source file for the module and loaded it.

因此,我们可以为这些使用不同的名称,这意味着我们可以使它像我们复制模块的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

Then in my_sub_module, I can import 'socket_patched' which does not exist on system! Here we are in my_sub_module.py.

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

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

#2


11  

This is pretty disgusting, but this might suffice:

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

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

#3


4  

Here's some code that creates a new module with the functions and variables of the old:

这里有一些代码可以创建一个带有旧函数和变量的新模块:

def copymodule(old):
    new = type(old)(old.__name__, old.__doc__)
    new.__dict__.update(old.__dict__)
    return new

Note that this does a fairly shallow copy of the module. The dictionary is newly created, so basic monkey patching will work, but any mutables in the original module will be shared between the two.

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

Edit: According to the comment, a deep copy is needed. I tried messing around with monkey-patching the copy module to support deep copies of modules, but that didn't work. Next I tried importing the module twice, but since modules are cached in sys.modules, that gave me the same module twice. Finally, the solution I hit upon was removing the modules from sys.modules after importing it the first time, then importing it again.

编辑:根据评论,需要深层复制。我试着乱搞猴子修补副本模块以支持模块的深层副本,但这不起作用。接下来我尝试导入模块两次,但由于模块缓存在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

#4


1  

Physically copy the socket module to socket_monkey and go from there? I don't feel you need any "clever" work-around... but I might well be over simplifying!

物理上将套接字模块复制到socket_monkey并从那里开始?我觉得你不需要任何“聪明”的解决方法......但我可能会过度简化!