¿Cómo importar un módulo dado su nombre como cadena?

543

Estoy escribiendo una aplicación Python que toma como 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 función __import__ , que toma una cadena para el nombre de un módulo:

__importar __ (nombre, globales = Ninguno, locales = Ninguno, de la lista = (), nivel = 0)

La función importa el nombre del módulo, potencialmente usando los globales y locales dados para determinar cómo interpretar el nombre en un contexto de paquete. La lista de origen proporciona los nombres de los objetos o submódulos que deben importarse del módulo dado por su nombre.

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 es posible que 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 usar 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.

Kamil Kisiel
fuente
¿Qué hace el fromlist = ["myapp.commands"]?
Pieter Müller
@ PieterMüller: en una cáscara de pitón escriba esto: dir(__import__). La lista from from debe ser una lista de nombres para emular "from name import ...".
mawimawi
A partir de 2019, debe buscar importlib: stackoverflow.com/a/54956419/687896
Brandt
No use __import__ vea Python Doc use importlib.import_module
fcm

Respuestas:

321

Con Python anterior a 2.7 / 3.1, así es como lo haces.

Para las versiones más recientes, ver importlib.import_modulepara Python 2 y y Python 3 .

Puedes usar execsi quieres también.

O utilizando __import__puede importar una lista de módulos haciendo esto:

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

Ripeado directamente de Dive Into Python .

Harley Holcombe
fuente
77
¿Cuál es la diferencia con el ejecutivo?
user1767754
1
¿Cómo podrías usar __init__ese módulo?
Dorian Dore
77
Un problema con esta solución para el OP es que la captura de excepciones para uno o dos módulos de "comando" incorrectos hace que su aplicación de comando completa falle en una excepción. Personalmente, me gustaría hacer un bucle sobre cada importación envuelta individualmente en un intento: mods = __ import __ () \ nexcept ImportError como error: informe (error) para permitir que otros comandos continúen funcionando mientras se corrigen los malos.
DevPlayer
77
Otro problema con esta solución, como señala Denis Malinovsky 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á destinada para el intérprete de Python y no para uso general, es mejor usar importlib.import_module () ..." Al usar python3, el impmódulo resuelve este problema, como se menciona a continuación en monkut.
LiavK
2
@JohnWu, ¿por qué lo necesitas foreachcuando tienes for element iny map()aunque :)
cowbert
300

La forma recomendada para Python 2.7 y 3.1 y posterior es usar el importlibmódulo:

importlib.import_module (nombre, paquete = Ninguno)

Importar un módulo. El argumento de 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 el 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
fuente
2
¿Recomendado por qué fuente o autoridad?
michuelnik
66
La documentación aconseja no utilizar la __import__función a favor del módulo mencionado anteriormente.
Denis Malinovsky
8
Esto funciona bien para la importación os.path; ¿qué tal from os.path import *?
Nam G VU
55
Obtengo la respuesta aquí stackoverflow.com/a/44492879/248616 es decir. llamandoglobals().update(my_module.__dict)
Nam G VU
2
@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
132

Nota: imp está en desuso desde Python 3.4 a 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 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
Monkut
fuente
2
Buena y simple 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 e 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
3
En la línea donde construye MyClass en el módulo de destino, está agregando una referencia redundante al nombre de la clase. Ya está almacenado en expected_classlo que podría hacer en su class_inst = getattr(py_mod,expected_name)()lugar.
Andrew
1
Tenga en cuenta que si existe un archivo compilado de bytes que coincida correctamente (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_source
cdosborn
1
Atención: esta solución funciona; sin embargo, el módulo imp será desaprobado a favor de import lib, consulte la página de imp: "" "Desaprobado desde la versión 3.4: El paquete imp está pendiente de desaprobación a favor de importlib." ""
Ricardo
15

Hoy en día deberías usar importlib .

Importar un archivo fuente

Los documentos realmente proporcionan 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 forma puede acceder a los miembros (por ejemplo, una función " hello") desde su módulo pluginX.py, en este fragmento que se llama module, bajo su espacio de nombres; Ej module.hello().

Si desea importar los miembros (por ejemplo, " hello") puede incluir module/ pluginXen la lista de módulos en memoria:

sys.modules[module_name] = module

from pluginX import hello
hello()

Importar un paquete

Importar un paquete ( por ejemplo , 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)
Brandt
fuente
3
agregue sys.modules[module_name] = moduleal final de su código si desea poder import module_namedespués
Daniel Braun
@DanielBraun haciéndome error> ModuleNotFoundError
Nam G VU
10

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()

Jonathan
fuente
2
La documentación para la locals()función incorporada advierte explícitamente que "el contenido de este diccionario no debe modificarse".
Martineau
9

Puedes usar exec:

exec("import myapp.commands.%s" % command)
Greg Hewgill
fuente
¿Cómo obtengo un identificador para el módulo a través del exec, de modo que pueda llamar (en este ejemplo) al método .run ()?
Kamil Kisiel
55
Luego puede hacer getattr (myapp.commands, command) para acceder al módulo.
Greg Hewgill el
1
... o agregue as command_moduleal final de la declaración de importación y luego hagacommand_module.run()
oxfn
En alguna versión de Python 3+, exec 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 ejecutado referenciado de los locales del bloque de código local, puede proporcionar su propio dict, como uno vacío y hacer referencia a las referencias usando ese dict, o pasar ese dict a otras funciones exec (source, gobals (), command1_dict) .... print (command1_dict ['somevarible'])
DevPlayer
2

Similar a la solución de @monkut pero reutilizable y tolerante a errores descrita 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
stamat
fuente
0

La siguiente pieza funcionó 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>'
PanwarS87
fuente
-2

Por ejemplo, los nombres de mis módulos son como jan_module/ feb_module/ mar_module.

month = 'feb'
exec 'from %s_module import *'%(month)
chiru
fuente
-7

Lo siguiente funcionó 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]())

Carga módulos de la carpeta 'modus'. Los módulos tienen una sola clase con el mismo nombre que el nombre del módulo. Por ejemplo, el archivo modus / module1.py contiene:

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

El resultado es una lista de clases cargadas dinámicamente "adaptadores".

Marc de Lignie
fuente
13
No entierres las respuestas sin dar una razón en los comentarios. No ayuda a nadie.
Rebs
Me gustaría saber por qué esto es algo malo.
DevPlayer
Igual que yo, ya que soy nuevo en Python y quiero saber por qué esto es malo.
Kosmos el
55
Esto es malo no solo porque es un mal pitón, sino que también es un código generalmente malo. ¿Qué es fl? La definición del bucle for es demasiado compleja. El camino está codificado (y, por ejemplo, es irrelevante). Está utilizando python desalentado ( __import__), ¿para qué sirve todo eso fl[i]? Esto es básicamente ilegible, y es innecesariamente complejo para algo que no es tan difícil: vea la respuesta más votada con su frase.
Phil