Подтвердить что ты не робот

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

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

Возможно ли это?

Я хочу действительно скопировать модуль, а именно получить тот же результат в runtime, как если бы я скопировал socketmodule.c, изменил функцию initsocket() на initmy_socket() и установил ее как расширение my_socket.

4b9b3361

Ответ 1

Вы всегда можете делать трюки, например, импортировать модуль, а затем удалять его из 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. Подробнее см. документация.

UPDATE:

Чтобы сделать основное отличие этого подхода очевидным, я хочу сделать это более понятным: если вы импортируете один и тот же модуль таким образом, вы будете иметь обе версии, доступные глобально для каждого другого модуля, который вы импортируете во время выполнения, что и есть то, что как я понял.

Ниже приведен еще один пример, чтобы подчеркнуть этот момент.

Эти два утверждения делают одно и то же:

import my_socket_module as socket_imported

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

Во второй строке мы повторяем строку 'my_socket_module' дважды, и так действует операция импорта; но эти две строки фактически используются по двум причинам.

Второе вхождение, когда мы передали его 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'

Ответ 2

Это довольно отвратительно, но этого может быть достаточно:

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 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

Вот код, который создает новый модуль с функциями и переменными старого:

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

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

Изменить: Согласно комментарию, требуется глубокая копия. Я попытался объединиться с обезьяной-патчей модуля 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

Ответ 4

Физически скопировать модуль сокета на socket_monkey и перейти оттуда? Я не чувствую, что вам нужно какое-то "умное" обходное... но я вполне мог бы упроститься!