Carga dinámica de clases en Python 2.6: RuntimeWarning: no se encontraron ‘complementos’ del módulo principal al manejar la importación absoluta

Estoy trabajando en un sistema de complementos donde los módulos de complementos se cargan así:

def load_plugins(): plugins=glob.glob("plugins/*.py") instances=[] for p in plugins: try: name=p.split("/")[-1] name=name.split(".py")[0] log.debug("Possible plugin: %s", name) f, file, desc=imp.find_module(name, ["plugins"]) plugin=imp.load_module('plugins.'+name, f, file, desc) getattr(plugin, "__init__")(log) instances=instances+plugin.get_instances() except Exception as e: log.info("Failed to load plugin: "+str(p)) log.info("Error: %s " % (e)) log.info(traceback.format_exc(e)) return instances 

El código funciona, pero para cada statement de importación en el código del complemento, aparece una advertencia como esta:

 plugins/plugin.py:2: RuntimeWarning: Parent module 'plugins' not found while handling absolute import import os 

No se informan errores para el código del progtwig principal, y los complementos funcionan.

¿Alguien puede explicar qué significa la advertencia y qué estoy haciendo mal? ¿Necesito crear un módulo de complementos vacío por separado e importarlo para mantener a Python Happy?

Si los plugins del directorio fueran un paquete real (contenía __init__.py fine), podría usar fácilmente pkgutils para enumerar sus archivos de complementos y cargarlos.

 import pkgutil # import our package import plugins list(pkgutil.iter_modules(plugins.__path__)) 

Sin embargo, puede funcionar sin un paquete de complementos de todos modos, intente esto:

 import pkgutil list(pkgutil.iter_modules(["plugins"])) 

También es posible hacer un paquete que solo exista en tiempo de ejecución:

 import types import sys plugins = types.ModuleType("plugins") plugins.__path__ = ["plugins"] sys.modules["plugins"] = plugins import plugins.testplugin 

Sin embargo, ese truco que fue sobre todo por diversión!

Si el directorio de complementos no tiene un __init__.py , no es un paquete, por lo que cuando crea __init__.py . __init__.py lo que sea, Python le advierte que tal cosa no debería existir realmente. (No se puede crear mediante ” import plugins.whatever ” sin importar cuál sea su ruta).

También,

  • No dividir en / , lo que es inportable. Utilice os.path.split .
  • No use .split(".py") para obtener el nombre sin la extensión, que tiene errores. Utilice os.path.splitext .
  • No use getattr con una cadena literal. getattr(plugin, "__init__") está escrito como plugin.__init__ .
  • Estoy confundido de por qué llama a una función __init__ nivel de __init__ . Esto no parece correcto. Quizás desee una función “set_logger” o mejor, para crear una instancia de una clase que toma un registrador.
  • No use L = L + some_other_list para extender una lista, use el método extend , que tiene un mejor rendimiento y es más idiomático.
  • No aplastar excepciones desconocidas por except Exception . Si no puede planear hacer algo sensato en respuesta a una excepción, su progtwig no puede continuar sanamente.

El problema aquí es con el punto (‘.’) En el nombre del módulo:

imp.load_module('plugins.'+name, f, file, desc)

No incluya un ‘.’ después de ‘plugins’, o Python pensará que es una ruta de módulo.

Puede intentar agregar la siguiente statement al comienzo de las declaraciones de importación.

 from __future__ import absolute_import 

La documentación de Python imp se ha actualizado desde que se respondió. Ahora aborda este problema específicamente en el método find_module() .

Esta función no maneja los nombres de módulos jerárquicos (nombres que contienen puntos). Para encontrar PM , es decir, el submódulo M del paquete P , use find_module() y load_module() para encontrar y cargar el paquete P , y luego use find_module() con el argumento path establecido en P.__path__ . Cuando P en sí tiene un nombre de puntos, aplique esta receta de forma recursiva.

Tenga en cuenta que P.__path__ ya es una lista cuando la proporciona a find_module() . También tenga en cuenta lo que dice la documentación find_module() sobre cómo encontrar paquetes.

Si el módulo es un paquete, el archivo es None , la ruta es la ruta del paquete y el último elemento de la tupla de descripción es PKG_DIRECTORY .

Entonces, a partir de la pregunta del OP, para importar el complemento sin las advertencias de RuntimeError , siga las instrucciones en la documentación actualizada de Python:

 # first find and load the package assuming it is in # the current working directory, '.' f, file, desc = imp.find_module('plugins', ['.']) pkg = imp.load_module('plugins', f, file, desc) # then find the named plugin module using pkg.__path__ # and load the module using the dotted name f, file, desc = imp.find_module(name, pkg.__path__) plugin = imp.load_module('plugins.' + name, f, file, desc)