Как сделать копию модуля python во время выполнения?

мне нужно сделать копию модуля сокета, чтобы иметь возможность использовать его и иметь еще один модуль сокета, исправленный обезьяной, и использовать его по-другому.

это возможно?

Я имею в виду действительно скопировать модуль, а именно получить тот же результат в время работы как будто я скопировал socketmodule.c, изменен на

4 ответов


вы всегда можете делать трюки, такие как импорт модуля, а затем удаление его из sys.модули или попытка скопировать модуль. Однако 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_module is deprecated в питон3.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"

здесь мы импортируем один и тот же модуль дважды, но как совершенно разные объекты модуля. Если вы проверите системный.модули, вы можете увидеть два имени, введенные в качестве первых параметров для вызовов 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

обратите внимание, что это делает довольно мелкую копию модуля. Словарь недавно создан, поэтому Основные исправления обезьян будут работать, но любые изменяемые в исходном модуле будут разделены между ними.

Edit: согласно комментарию, требуется глубокая копия. Я попытался возиться с обезьяной-латать 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 и перейдите оттуда? Я не думаю, что тебе нужна "умная" работа... но, возможно, я слишком упрощаю!