¿Cómo puedo importar un módulo dinámicamente dado su nombre como cadena?

Resuelto Kamil Kisiel asked hace 16 años • 10 respuestas

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 dado globalsy localspara determinar cómo interpretar el nombre en el contexto de un paquete. Proporciona fromlistlos nombres de los objetos o submódulos que deben importarse desde el módulo proporcionado por name.

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?

Kamil Kisiel avatar Nov 19 '08 13:11 Kamil Kisiel
Aceptado

Con Python anterior a 2.7/3.1, así es como se hace.

Para versiones más recientes, consulte importlib.import_modulePython 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 .

Harley Holcombe avatar Nov 19 '2008 06:11 Harley Holcombe

La forma recomendada para Python 2.7 y 3.1 y posteriores es utilizar importlibel 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.modo ..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')
Denis Malinovsky avatar Dec 22 '2012 07:12 Denis Malinovsky

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
monkut avatar Nov 19 '2008 08:11 monkut

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í, modulehabrá un objeto de módulo que representará el pluginXmódulo (lo mismo que se asignaría pluginXhaciendo import pluginX). Por lo tanto, para llamar, por ejemplo, a una hellofunción (sin parámetros) definida en pluginX, utilice module.hello().

Para obtener el efecto de "importar" la funcionalidad fromdel módulo, guárdelo en la memoria caché de los módulos cargados y luego realice la fromimportación correspondiente:

sys.modules[module_name] = module

from pluginX import hello
hello()

Importando un paquete

Para importar un paquete, import_modulebasta con llamar. Supongamos que hay una carpeta de paquetes pluginXen el directorio de trabajo actual; entonces solo hazlo

import importlib

pkg = importlib.import_module('pluginX')

# check if it's all there..
print(dir(pkg))
Brandt avatar Mar 02 '2019 07:03 Brandt

Utilice el módulo imp o la __import__()función más directa.

gimel avatar Nov 19 '2008 06:11 gimel