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

9 minutos de lectura

Avatar de usuario de Kamil Kisiel
kamil kisiel

Estoy escribiendo una aplicación de Python que toma un comando como argumento, por ejemplo:

$ python myapp.py command1

Quiero que la aplicación sea extensible, es decir, poder 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

Así que quiero que la aplicación encuentre los módulos de comando disponibles en tiempo de ejecución y ejecute el apropiado.

Python define un __import__() función, que toma una cadena para un nombre de módulo:

__import__(name, globals=None, locals=None, fromlist=(), level=0)

La función importa el módulo. namepotencialmente usando el dado globals y locals para determinar cómo interpretar el nombre en un contexto de paquete. los fromlist da los nombres de los objetos o submódulos que deben importarse del módulo dado por name.

Fuente: https://docs.python.org/3/library/functions.html#__import__

Así que 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, solo me pregunto si posiblemente haya una forma más idiomática de lograr lo que estamos haciendo con este código.

Tenga en cuenta que específicamente no quiero entrar en el 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.


Ver también: ¿Cómo importo un módulo dada la ruta completa?

  • ¿Qué significa fromlist=[“myapp.commands”] ¿hacer?

    – Pieter Muller

    6 de junio de 2012 a las 10:29

  • @PieterMüller: en un shell de Python, escriba esto: dir(__import__). La fromlist debe ser una lista de nombres para emular “from name import…”.

    – mawimawi

    9 de agosto de 2012 a las 7:13

  • Importar módulo desde variable de cadena

    – tormenta_m2138

    12 de agosto de 2015 a las 18:28

  • A partir de 2019, debe buscar importlib: stackoverflow.com/a/54956419/687896

    – Brandt

    13 de marzo de 2019 a las 9:42

  • No use __import__ ver Documento de Python utilizar una importlib.import_module

    – fcm

    28 junio 2019 a las 15:00

Avatar de usuario de Harley Holcombe
harley holcombe

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

Para versiones más recientes, consulte importlib.import_module por Pitón 2 y Pitón 3.

Puedes usar exec si tu tambien quieres.

o usando __import__ puede importar una lista de módulos haciendo esto:

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

Arrancado directamente de Sumérgete en Python.

  • ¿Cuál es la diferencia con exec?

    – usuario1767754

    17 de septiembre de 2014 a las 7:10

  • Un problema con esta solución para el OP es que atrapar excepciones para uno o dos módulos de “comando” defectuosos hace que toda su aplicación de comando falle en una excepción. Personalmente, haría un bucle sobre cada importar envuelto individualmente en un intento: mods=__import__()\nexcepto ImportError como error: informe (error) para permitir que otros comandos continúen funcionando mientras se arreglan los malos.

    – DevPlayer

    08/04/2015 a las 13:38

  • Otro problema con esta solución, como Denis Malinovsky señala a continuación, es que los propios documentos de Python recomiendan no usar __import__. Los documentos 2.7: “Debido a que esta función está diseñada para que la use el intérprete de Python y no para uso general, es mejor usar importlib.import_module ()…” Al usar python3, el imp El módulo resuelve este problema, como menciona monkut a continuación.

    – LiavK

    8 de julio de 2015 a las 15:14


  • @JohnWu ¿por qué necesitas foreach cuando tengas for element in y map() aunque 🙂

    – cowbert

    19 de febrero de 2018 a las 22:59

  • Tenga en cuenta que el mapa NO funcionará en python 3, ya que el mapa ahora usa una evaluación diferida.

    – qué

    13 de noviembre de 2018 a las 21:52

Avatar de usuario de Denis Malinovsky
Denis Malinovsky

los recomendado forma para Python 2.7 y 3.1 y posteriores es usar importlib módulo:

importlib.import_module(name, package=None)

Importar un módulo. El argumento del 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 (p. ej. import_module('..mod', 'pkg.subpkg') importará pkg.mod).

p.ej

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

  • ¿Recomendado por qué fuente o autoridad?

    – michuelnik

    08/04/2015 a las 13:21

  • Consejos de documentación contra usando __import__ función a favor del módulo mencionado anteriormente.

    – Denis Malinovsky

    9 de abril de 2015 a las 12:42

  • Esto funciona bien para la importación. os.path ; qué tal si from os.path import *?

    – Nam G VU

    12 de junio de 2017 a las 6:31

  • Obtengo la respuesta aquí stackoverflow.com/a/44492879/248616 es decir. vocación globals().update(my_module.__dict)

    – Nam G VU

    12 de junio de 2017 a las 6:46


  • @NamGVU, este es un método peligroso ya que está contaminando sus globales y puede anular cualquier identificador con los mismos nombres. El enlace que ha publicado tiene una versión mejor y mejorada de este código.

    – Denis Malinovsky

    19 de junio de 2017 a las 17:11


avatar de usuario de monkut
monje

Nota: diablillo está en desuso desde Python 3.4 a favor de importlib

Como se mencionó el diablillo El módulo le proporciona funciones de carga:

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

He usado estos antes para realizar algo similar.

En mi caso, definí una clase específica con los métodos definidos que se requerían. 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

  • Buena y sencilla solución. Escribí uno similar: stamat.wordpress.com/dynamic-module-import-in-python Pero el tuyo tiene algunos defectos: ¿Qué pasa con las excepciones? IOError y ImportError? ¿Por qué no buscar primero la versión compilada y luego la versión fuente? Esperar una clase reduce la reutilización en su caso.

    – stamat

    30/06/2013 a las 20:40

  • En la línea donde construye MyClass en el módulo de destino, está agregando una referencia redundante al nombre de la clase. ya esta guardado en expected_class para que puedas hacer class_inst = getattr(py_mod,expected_name)() en cambio.

    – Andrés

    16 de octubre de 2013 a las 7:11

  • Tenga en cuenta que si existe un archivo compilado de bytes que coincida correctamente (con el sufijo .pyc o .pyo), se usará en lugar de analizar el archivo fuente dado.. https://docs.python.org/2/library/imp.html#imp.load_source

    – cdosborn

    30 de junio de 2015 a las 21:07


  • Atención: esta solución funciona; sin embargo, el módulo imp quedará obsoleto a favor de import lib, consulte la página de imp: “””Obsoleto desde la versión 3.4: el paquete imp está pendiente de obsolescencia a favor de importlib.”””

    – nsx

    20 de noviembre de 2016 a las 9:50

Avatar de usuario de Brandt
Brandt

Hoy en día deberías usar importlib.

Importar un archivo fuente

los documentos en realidad proporciona una receta para eso, y dice así:

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)

# check if it's all there..
def bla(mod):
    print(dir(mod))
bla(module)

De esta manera puede acceder a los miembros (por ejemplo, una función “hello“) de su módulo pluginX.py — en este fragmento llamado module — bajo su espacio de nombres; P.ej, module.hello().

Si desea importar los miembros (p. ej., “hello“) puede incluir module/pluginX en la lista de módulos en memoria:

sys.modules[module_name] = module

from pluginX import hello
hello()

Importar un paquete

Importación de un paquete (p.ej, pluginX/__init__.py) bajo su directorio actual es realmente sencillo:

import importlib

pkg = importlib.import_module('pluginX')

# check if it's all there..
def bla(mod):
    print(dir(mod))
bla(pkg)

Utilizar el módulo diablilloo el más directo __import__() función.

Avatar de usuario de Jonathan Livni
jonathan livni

Si lo quieres en tus 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)]'

lo mismo funcionaría con globals()

Avatar de usuario de Georgy
georgia

Puedes usar exec:

exec("import myapp.commands.%s" % command)

  • ¿Cómo obtengo un identificador del módulo a través del exec, de modo que pueda llamar (en este ejemplo) al método .run()?

    – Kamil Kisiel

    19 de noviembre de 2008 a las 6:19

  • Luego puede hacer getattr(myapp.commands, command) para acceder al módulo.

    – Greg Hewgill

    19 de noviembre de 2008 a las 6:20

  • … o añadir as command_module hasta el final de la declaración de importación y luego hacer command_module.run()

    – buey

    16/10/2013 a las 10:43


  • En alguna versión de Python 3+ ejecutivo se convirtió en una función con el beneficio adicional de que la referencia resultante creada en la fuente se almacena en el argumento locals() de exec(). Para aislar aún más el ejecutivo al que se hace referencia de los locales del bloque de código local, puede proporcionar su propio dictado, como uno vacío y hacer referencia a las referencias usando ese dictado, o pasar ese dictado a otras funciones exec (fuente, gobals(), command1_dict) …. imprimir (comando1_dict[‘somevarible’])

    – DevPlayer

    08/04/2015 a las 13:43

¿Ha sido útil esta solución?