Evita que Python almacene en caché los módulos importados

Al desarrollar un proyecto grande (dividido en varios archivos y carpetas) en Python con IPython, me encuentro con el problema de los módulos importados en caché.

El problema es que las instrucciones del import module solo leen el módulo una vez, incluso si ese módulo ha cambiado. Entonces, cada vez que cambio algo en mi paquete, tengo que salir y reiniciar IPython. Doloroso.

¿Hay alguna forma de forzar correctamente la recarga de algunos módulos? O, mejor, ¿de alguna manera evitar que Python los almacene en caché?

Probé varios enfoques, pero ninguno funciona. En particular, me encuentro con errores realmente extraños, como que algunos módulos o variables se vuelven misteriosamente iguales a None

El único recurso razonable que encontré es la recarga de módulos de Python , desde pyunit, pero no lo he comprobado. Me gustaría algo así.

Una buena alternativa sería que IPython se reinicie, o reinicie el intérprete de Python de alguna manera.

Entonces, si desarrolla en Python, ¿qué solución ha encontrado para este problema?

Editar

Para aclarar las cosas: obviamente, entiendo que algunas variables antiguas que dependen del estado anterior del módulo pueden quedarse. Eso está bien para mi. ¿Por qué es tan difícil en Python forzar la recarga de un módulo sin que ocurran errores extraños?

Más específicamente, si tengo todo mi módulo en un archivo module.py , lo siguiente funciona bien:

 import sys try: del sys.modules['module'] except AttributeError: pass import module obj = module.my_class() 

Este código funciona a la perfección y puedo desarrollarlo sin abandonar IPython durante meses.

Sin embargo , cada vez que mi módulo consta de varios submódulos, el infierno se desata:

 import os for mod in ['module.submod1', 'module.submod2']: try: del sys.module[mod] except AttributeError: pass # sometimes this works, sometimes not. WHY? 

¿Por qué es tan diferente para Python si tengo mi módulo en un archivo grande o en varios submódulos? ¿Por qué no funcionaría ese enfoque?

Dejar de fumar y reiniciar el intérprete es la mejor solución. Cualquier tipo de estrategia de recarga en vivo o sin almacenamiento en caché no funcionará a la perfección porque pueden existir objetos de módulos que ya no existen y porque los módulos a veces almacenan el estado y porque incluso si su caso de uso realmente permite la recarga en caliente, es demasiado complicado pensar valer la pena.

import controles para ver si el módulo está en sys.modules , y si lo está, lo devuelve. Si desea importar para cargar el módulo recién salido del disco, primero puede eliminar la clave correspondiente en sys.modules .

Existe la función incorporada de reload que, dado un objeto de módulo, lo volverá a cargar desde el disco y se colocará en sys.modules . Editar : en realidad, volverá a comstackr el código del archivo en el disco y luego lo reevaluará en el __dict__ del módulo existente. Algo potencialmente muy diferente a hacer un nuevo objeto de módulo.

Mike Graham tiene razón, sin embargo; es difícil volver a cargar correctamente si tiene incluso algunos objetos vivos que hacen referencia al contenido del módulo que ya no quiere. Los objetos existentes seguirán haciendo referencia a las clases de las que se crearon instancias, es un problema obvio, pero también todas las referencias creadas por medio del from module import symbol apuntarán a cualquier objeto de la versión anterior del módulo. Muchas cosas sutilmente equivocadas son posibles.

Edit: Estoy de acuerdo con el consenso de que reiniciar el intérprete es, con mucho, lo más confiable. Pero para propósitos de depuración, supongo que podrías intentar algo como lo siguiente. Estoy seguro de que hay casos de esquinas en los que esto no funcionaría, pero si no estás haciendo nada demasiado loco (de lo contrario) con la carga del módulo en tu paquete, podría ser útil.

 def reload_package(root_module): package_name = root_module.__name__ # get a reference to each loaded module loaded_package_modules = dict([ (key, value) for key, value in sys.modules.items() if key.startswith(package_name) and isinstance(value, types.ModuleType)]) # delete references to these loaded modules from sys.modules for key in loaded_package_modules: del sys.modules[key] # load each of the modules again; # make old modules share state with new modules for key in loaded_package_modules: print 'loading %s' % key newmodule = __import__(key) oldmodule = loaded_package_modules[key] oldmodule.__dict__.clear() oldmodule.__dict__.update(newmodule.__dict__) 

Lo cual probé muy brevemente como tal:

 import email, email.mime, email.mime.application reload_package(email) 

impresión:

 reloading email.iterators reloading email.mime reloading email.quoprimime reloading email.encoders reloading email.errors reloading email reloading email.charset reloading email.mime.application reloading email._parseaddr reloading email.utils reloading email.mime.base reloading email.message reloading email.mime.nonmultipart reloading email.base64mime 

Con IPython viene la extensión de carga automática que repite automáticamente una importación antes de cada llamada de función. Funciona al menos en casos simples, pero no confíe demasiado en él: de acuerdo con mi experiencia, se requiere un reinicio del intérprete de vez en cuando, especialmente cuando los cambios de código se producen solo en el código importado indirectamente.

Ejemplo de uso de la página enlazada:

 In [1]: %load_ext autoreload In [2]: %autoreload 2 In [3]: from foo import some_function In [4]: some_function() Out[4]: 42 In [5]: # open foo.py in an editor and change some_function to return 43 In [6]: some_function() Out[6]: 43 

Ya hay algunas respuestas realmente buenas aquí, pero vale la pena saber acerca de dreload, que es una función disponible en IPython que funciona como “recarga profunda”. De la documentación:

El módulo IPython.lib.deepreload le permite recursivamente recargar un módulo: los cambios realizados en cualquiera de sus dependencias se volverán a cargar sin tener que salir. Para empezar a usarlo, haz:

http://ipython.org/ipython-doc/dev/interactive/reference.html#dreload

Está disponible como un “global” en el cuaderno de IPython (al menos mi versión, que ejecuta v2.0).

HTH

Puede usar la maquinaria de enganche de importación descrita en PEP 302 para cargar, no los módulos, sino algún tipo de objeto proxy que le permita hacer lo que quiera con el objeto de módulo subyacente: volver a cargarlo, quitarle la referencia, etc.

El beneficio adicional es que su código existente actual no requerirá cambios y esta funcionalidad del módulo adicional se puede arrancar desde un solo punto en el código, donde en realidad agrega buscador a sys.meta_path .

Algunas ideas sobre la implementación: cree un buscador que acepte encontrar cualquier módulo, excepto el incorporado (no tiene nada que ver con los módulos integrados), luego cree el cargador que devolverá el objeto proxy subclasificado de types.ModuleType lugar del objeto de módulo real. Tenga en cuenta que los objetos del cargador no están obligados a crear referencias explícitas a los módulos cargados en sys.modules , pero se recomienda encarecidamente porque, como ya ha visto, puede fallar de forma inesperada. El objeto proxy debe capturar y reenviar todos los __getattr__ , __setattr__ y __delattr__ al módulo real subyacente al que mantiene la referencia. Es probable que no necesite definir __getattribute__ debido a que no ocultaría el contenido real de los módulos con sus métodos proxy. Por lo tanto, ahora debe comunicarse con el proxy de alguna manera: puede crear algún método especial para eliminar la referencia subyacente, luego importar el módulo, extraer la referencia del proxy devuelto, eliminar el proxy y retener la referencia al módulo recargado. Few, parece aterrador, pero debería solucionar su problema sin tener que volver a cargar Python cada vez.

Estoy usando PythonNet en mi proyecto. Afortunadamente, encontré que hay un comando que puede resolver perfectamente este problema.

 using (Py.GIL()) { dynamic mod = Py.Import(this.moduleName); if (mod == null) throw new Exception( string.Format("Cannot find module {0}. Python script may not be complied successfully or module name is illegal.", this.moduleName)); // This command works perfect for me! PythonEngine.ReloadModule(mod); dynamic instance = mod.ClassName(); 

Piensa dos veces para dejar de fumar y reiniciar en producción.

La solución fácil sin salir y reiniciar es usando la recarga de imp

 import moduleA, moduleB from imp import reload reload (moduleB) 

Para Python versión 3.4 y superior

 import importlib importlib.reload() from  import  

Consulte la documentación a continuación para más detalles.