¿Cómo hacer una copia de un módulo de python en tiempo de ejecución?

Necesito hacer una copia de un módulo de socket para poder usarlo y tener un módulo de socket más parcheado y usarlo de manera diferente.

es posible?

Quiero copiar realmente un módulo, es decir, para obtener el mismo resultado en tiempo de ejecución como si hubiera copiado socketmodule.c , cambié la función initsocket() a initmy_socket() , y la instalé como la extensión my_socket .

Siempre puedes hacer trucos como importar un módulo y luego borrarlo de sys.modules o intentar copiar un módulo. Sin embargo, Python ya proporciona lo que quieres en su biblioteca estándar.

 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 está en desuso en python3.3 + , y recomienda el uso de 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" 

Aquí, importamos el mismo módulo dos veces pero como objetos de módulos completamente diferentes. Si verifica sys.modules, puede ver dos nombres que ingresó como primer parámetro para las llamadas load_module. Echa un vistazo a la documentación para más detalles.

ACTUALIZAR:

Para que la diferencia principal de este enfoque sea obvia, quiero que sea más claro: cuando importe el mismo módulo de esta manera, tendrá ambas versiones accesibles globalmente para todos los demás módulos que importe en tiempo de ejecución, que es exactamente lo que necesita el interrogador. Entendí.

A continuación se muestra otro ejemplo para enfatizar este punto.

Estas dos afirmaciones hacen exactamente lo mismo:

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

En la segunda línea, repetimos la cadena ‘my_socket_module’ dos veces y así es como funciona la statement de importación; pero estas dos cadenas, de hecho, se utilizan por dos razones diferentes.

La segunda vez que lo pasamos a find_module se usa como nombre de archivo que se encontrará en el sistema. La primera aparición de la cadena como la pasamos al método load_module se usa como un identificador de todo el sistema del módulo cargado .

Por lo tanto, podemos usar nombres diferentes para estos, lo que significa que podemos hacer que funcione exactamente igual que copiamos el archivo fuente de Python para el módulo y lo cargamos.

 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 

Luego, en my_sub_module, puedo importar ‘socket_patched’ que no existe en el sistema. Aquí estamos en my_sub_module.py.

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

Esto es bastante desagradable, pero esto podría ser suficiente:

 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 

Aquí hay algunos códigos que crean un nuevo módulo con las funciones y variables de los anteriores:

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

Tenga en cuenta que esto hace una copia bastante superficial del módulo. El diccionario se acaba de crear, por lo que el parcheo básico de monos funcionará, pero cualquier mutables en el módulo original se compartirá entre los dos.

Edit: De acuerdo con el comentario, se necesita una copia profunda. Intenté jugar con el parche de mono en el módulo de copy para admitir copias en profundidad de los módulos, pero eso no funcionó. Luego intenté importar el módulo dos veces, pero como los módulos se almacenan en caché en sys.modules , eso me dio el mismo módulo dos veces. Finalmente, la solución que encontré fue eliminar los módulos de sys.modules después de importarlos por primera vez y luego volver a importarlos.

 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 

¿Copiar físicamente el módulo de socket a socket_monkey y continuar desde allí? No creo que necesites ninguna solución “inteligente” … ¡pero podría estar simplificando!