¿Cómo puedo importar un módulo dinámicamente dado su nombre como cadena?
Estoy escribiendo una aplicación Python que toma un comando como argumento, por ejemplo:
$ python myapp.py command1
Quiero que la aplicación sea extensible, es decir, que pueda agregar nuevos módulos que implementen nuevos comandos sin tener que cambiar la fuente principal de la aplicación. El árbol se parece a:
myapp/
__init__.py
commands/
__init__.py
command1.py
command2.py
foo.py
bar.py
Entonces quiero que la aplicación encuentre los módulos de comando disponibles en tiempo de ejecución y ejecute el apropiado.
Python define una __import__()
función que toma una cadena para el nombre de un módulo:
__import__(name, globals=None, locals=None, fromlist=(), level=0)
La función importa el módulo
name
, potencialmente utilizando lo dadoglobals
ylocals
para determinar cómo interpretar el nombre en el contexto de un paquete. Proporcionafromlist
los nombres de los objetos o submódulos que deben importarse desde el módulo proporcionado porname
.Fuente: https://docs.python.org/3/library/functions.html#__import_ _
Actualmente tengo 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 bien, sólo me pregunto si existe posiblemente una forma más idiomática de lograr lo que estamos haciendo con este código.
Tenga en cuenta que específicamente no quiero utilizar 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.
Ver también: ¿ Cómo importo un módulo dada la ruta completa?
Con Python anterior a 2.7/3.1, así es como se hace.
Para versiones más recientes, consulte importlib.import_module
Python 2 y Python 3 .
O usando __import__
puedes importar una lista de módulos haciendo esto:
>>> moduleNames = ['sys', 'os', 're', 'unittest']
>>> moduleNames
['sys', 'os', 're', 'unittest']
>>> modules = map(__import__, moduleNames)
Extraído directamente de Dive Into Python .
La forma recomendada para Python 2.7 y 3.1 y posteriores es utilizar importlib
el módulo:
importlib.import_module(
name, package=None)
Importar un módulo. El argumento nombre especifica qué módulo importar en términos absolutos o relativos (por ejemplo,
pkg.mod
o..mod
). Si el nombre se especifica en términos relativos, entonces el argumento del paquete debe establecerse en el nombre del paquete que actuará como ancla para resolver el nombre del paquete (por ejemplo,import_module('..mod', 'pkg.subpkg')
importarápkg.mod
).
p.ej
my_module = importlib.import_module('os.path')
Nota: imp está en desuso desde Python 3.4 en favor de importlib
Como se mencionó, el módulo imp le proporciona funciones de carga:
imp.load_source(name, path)
imp.load_compiled(name, path)
Los he usado antes para realizar algo similar.
En mi caso definí una clase específica con los métodos definidos que eran necesarios. Una vez que cargué el módulo, verificaría si la clase estaba en el módulo y luego crearía 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
Usando importaciónlib
Importar un archivo fuente
Aquí hay un ejemplo ligeramente adaptado de la documentación :
import sys
import importlib.util
file_path = 'pluginX.py'
module_name = 'pluginX'
spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
# Verify contents of the module:
print(dir(module))
A partir de aquí, module
habrá un objeto de módulo que representará el pluginX
módulo (lo mismo que se asignaría pluginX
haciendo import pluginX
). Por lo tanto, para llamar, por ejemplo, a una hello
función (sin parámetros) definida en pluginX
, utilice module.hello()
.
Para obtener el efecto de "importar" la funcionalidad from
del módulo, guárdelo en la memoria caché de los módulos cargados y luego realice la from
importación correspondiente:
sys.modules[module_name] = module
from pluginX import hello
hello()
Importando un paquete
Para importar un paquete, import_module
basta con llamar. Supongamos que hay una carpeta de paquetes pluginX
en el directorio de trabajo actual; entonces solo hazlo
import importlib
pkg = importlib.import_module('pluginX')
# check if it's all there..
print(dir(pkg))
Utilice el módulo imp o la __import__()
función más directa.