2008-11-19 32 views
363

Estoy escribiendo una aplicación Python que toma como un comando como argumento, por ejemplo:módulo dinámico en Python

$ python myapp.py command1 

Quiero que la aplicación sea extensible, es decir, que sea capaz de añadir nuevos módulos que implementan nuevos comandos sin tener que cambiar el origen de la aplicación principal. El árbol se ve algo como:

myapp/ 
    __init__.py 
    commands/ 
     __init__.py 
     command1.py 
     command2.py 
    foo.py 
    bar.py 

Así que desea que la aplicación para encontrar los módulos de comando disponibles en tiempo de ejecución y ejecutar el apropiado.

Actualmente esto se implementa algo como:

command = sys.argv[1] 
try: 
    command_module = __import__("myapp.commands.%s" % command, fromlist=["myapp.commands"]) 
except ImportError: 
    # Display error message 

command_module.run() 

Esto funciona muy bien, me pregunto si hay posiblemente una forma más idiomática para lograr lo que estamos haciendo con este código.

Tenga en cuenta que específicamente no quiero entrar al uso de huevos o puntos de extensión. Este no es un proyecto de código abierto y no espero que haya "complementos". El objetivo es simplificar el código de la aplicación principal y eliminar la necesidad de modificarlo cada vez que se agrega un nuevo módulo de comando.

+0

¿Qué dice la fromlist = [ "myapp.commands"] hacer? –

+0

@ PieterMüller: en un shell python, escriba esto: '' dir (__ import __) ''. La lista de salida debe ser una lista de nombres para emular "de importación de nombre ...". – mawimawi

+2

[Importar módulo de variable de cadena] (http://stackoverflow.com/questions/8718885/import-module-from-string-variable) –

Respuesta

228

Con Python mayores de 2.7/3.1, que es más o menos la forma de hacerlo. Para versiones más nuevas, vea importlib.import_modulefor 2.7+ y for 3.1+.

Puedes usar exec si quieres también.

Nota puede importar una lista de módulos al hacer esto:

>>> moduleNames = ['sys', 'os', 're', 'unittest'] 
>>> moduleNames 
['sys', 'os', 're', 'unittest'] 
>>> modules = map(__import__, moduleNames) 

Ripped directamente desde Dive Into Python.

+5

¿Cuál es la diferencia con respecto al ejecutivo? – user1767754

+0

¿Cómo podría usar '__init__' de ese módulo? –

+2

Un problema con esta solución para el OP es que las excepciones de solapado para uno o dos módulos de "comando" incorrectos hacen que su aplicación de comando completa falle en una excepción. Personalmente, me gustaría repetir cada __import__ envuelto individualmente en una prueba: mods = __ import __() \ nexcept ImportError como error: informe (error) para permitir que otros comandos continúen funcionando mientras que los malos se arreglan. – DevPlayer

9

Puede utilizar exec:

exec "import myapp.commands.%s" % command 
+0

¿Cómo puedo obtener un control del módulo a través del ejecutivo, para que pueda llamar (en este ejemplo) el método .run() –

+5

Puede hacer getattr (myapp.commands, command) para acceder al módulo. –

+1

... o agregue 'como command_module' al final de la declaración de importación y luego haga' command_module.run() ' – oxfn

117

Como se ha mencionado el módulo imp le proporciona las funciones de carga:

imp.load_source(name, path) 
imp.load_compiled(name, path) 

He usado estos antes de realizar algo similar.

En mi caso, definí una clase específica con los métodos definidos que se requerían. Una vez cargado el módulo de E comprobaría si la clase estaba en el módulo, y luego crear una instancia de esa clase, algo como esto:

import imp 
import os 

def load_from_file(filepath): 
    class_inst = None 
    expected_class = 'MyClass' 

    mod_name,file_ext = os.path.splitext(os.path.split(filepath)[-1]) 

    if file_ext.lower() == '.py': 
     py_mod = imp.load_source(mod_name, filepath) 

    elif file_ext.lower() == '.pyc': 
     py_mod = imp.load_compiled(mod_name, filepath) 

    if hasattr(py_mod, expected_class): 
     class_inst = getattr(py_mod, expected_class)() 

    return class_inst 
+1

Solución buena y simple. Escribí uno similar: http://stamat.wordpress.com/dynamic-module-import-in-python/ Pero tu tiene algunos defectos: ¿y las excepciones? IOError e ImportError? ¿Por qué no verificar primero la versión compilada y luego la versión de origen? Esperar una clase reduce la reutilización en su caso. – stamat

+2

En la línea donde construye MyClass en el módulo de destino, está agregando una referencia redundante al nombre de clase. Ya está almacenado en 'expected_class', por lo que podría hacer' class_inst = getattr (py_mod, expected_name)() 'en su lugar. – Amoss

+1

* Tenga en cuenta que si existe un archivo compilado de bytes que coincida adecuadamente (con el sufijo .pyc o .pyo), se utilizará en lugar de analizar el archivo fuente dado *. [https://docs.python.org/2/library/imp.html#imp.load_loading](https://docs.python.org/2/library/imp.html#imp.load_loading) – cdosborn

0

Suena como lo que realmente quiere es una arquitectura de plugins.

Debe echar un vistazo a la funcionalidad entry points proporcionada por el paquete setuptools. Ofrece una excelente manera de descubrir los complementos que se cargan para su aplicación.

+6

mencioné específicamente que no lo hago desea utilizar puntos de entrada y una arquitectura de tipo de complemento. La razón del diseño es más de mantenimiento del código y modularidad en lugar de permitir que se incluyan complementos arbitrarios. –

-6

A continuación trabajó para mí:

import sys, glob 
sys.path.append('/home/marc/python/importtest/modus') 
fl = glob.glob('modus/*.py') 
modulist = [] 
adapters=[] 
for i in range(len(fl)): 
    fl[i] = fl[i].split('/')[1] 
    fl[i] = fl[i][0:(len(fl[i])-3)] 
    modulist.append(getattr(__import__(fl[i]),fl[i])) 
    adapters.append(modulist[i]()) 

Se carga los módulos de la carpeta 'operandi'. Los módulos tienen una sola clase con el mismo nombre que el nombre del módulo. P.ej. el modus archivo/modu1.py contiene:

class modu1(): 
    def __init__(self): 
     self.x=1 
     print self.x 

El resultado es una lista de clases de "adaptadores" carga dinámica.

+12

No entierre las respuestas sin proporcionar una razón en los comentarios. No ayuda a nadie. – Rebs

+0

Me gustaría saber por qué esto es malo. – DevPlayer

+0

Igual que yo, ya que soy nuevo en Python y quiero saber por qué es malo. – Kosmos

13

Si lo desea, en sus locales:

>>> mod = 'sys' 
>>> locals()['my_module'] = __import__(mod) 
>>> my_module.version 
'2.6.6 (r266:84297, Aug 24 2010, 18:46:32) [MSC v.1500 32 bit (Intel)]' 

misma trabajaría con globals()

206

La forma recomendada para Python 2.7 y más tarde es utilizar importlib módulo:

my_module = importlib.import_module('os.path') 
+2

¿Recomendado por qué fuente o autoridad? – michuelnik

+42

La documentación aconseja [en contra] (https://docs.python.org/2/library/functions.html#__import__) mediante la función '__import__' a favor del módulo mencionado anteriormente. –

+5

Esto funciona bien para importar 'os.path'; ¿Qué tal 'from os.path import *'? –

3

similares como la solución de @monkut pero reutilizable y tolerante a errores descrito aquí http://stamat.wordpress.com/dynamic-module-import-in-python/:

import os 
import imp 

def importFromURI(uri, absl): 
    mod = None 
    if not absl: 
     uri = os.path.normpath(os.path.join(os.path.dirname(__file__), uri)) 
    path, fname = os.path.split(uri) 
    mname, ext = os.path.splitext(fname) 

    if os.path.exists(os.path.join(path,mname)+'.pyc'): 
     try: 
      return imp.load_compiled(mname, uri) 
     except: 
      pass 
    if os.path.exists(os.path.join(path,mname)+'.py'): 
     try: 
      return imp.load_source(mname, uri) 
     except: 
      pass 

    return mod 
0

La pieza más adelante trabajó para mí:

>>>import imp; 
>>>fp, pathname, description = imp.find_module("/home/test_module"); 
>>>test_module = imp.load_module("test_module", fp, pathname, description); 
>>>print test_module.print_hello(); 

si desea importar en shell-script:

python -c '<above entire code in one line>'