La importación de un módulo desde una variable de cadena usando “__import__” da resultados diferentes a los de una instrucción de importación normal

4 minutos de lectura

avatar de usuario de theta
theta

Estoy trabajando en una documentación (personal) para la biblioteca anidada matplotlib (MPL), que difiere de la propia MPL proporcionada, por paquetes de submódulos interesados. Estoy escribiendo un script de Python que espero automatice la generación de documentos de futuras versiones de MPL.

Seleccioné submódulos/paquetes interesados ​​y quiero enumerar sus clases principales a partir de las cuales generaré una lista y la procesaré con pydoc.

El problema es que no puedo encontrar una manera de indicarle a Python que cargue un submódulo de una cadena. Aquí hay un ejemplo de lo que probé:

import matplotlib.text as text
x = dir(text)
i = __import__('matplotlib.text')
y = dir(i)
j = __import__('matplotlib')
z = dir(j)

Y aquí hay una comparación de 3 vías de las listas anteriores a través de pprint:

ingrese la descripción de la imagen aquí

No entiendo lo que está cargado y objeto – es base matplotlib además de algo más, pero le falta la información que quería y que son las clases principales de matplotlib.text paquete. Es la parte superior de color azul en la captura de pantalla (x lista).

  • ¿Puedes explicar por qué necesitas usar __import__(str) en lugar del estándar import declaración?

    – el mismo

    3 de enero de 2012 a las 21:53

  • Es porque procesaré listas cuyos elementos son submódulos MPL y obtendré las rutas de sus métodos.

    – theta

    03/01/2012 a las 22:00

  • @thesamet – vamos – hay un sinfín de ideas en las que le gustaría esta funcionalidad. Cuando tiene una configuración textual de bibliotecas, puede cargarlas por nombre, lo que no funcionaría del todo con el import declaración. He aquí un ejemplo de uso: djangosnippets.org/snippets/3048

    – Tomasz Gandor

    12 de mayo de 2014 a las 4:55

avatar de usuario de mzjn
mzjn

El __import__ La función puede ser un poco difícil de entender.

si cambias

i = __import__('matplotlib.text')

a

i = __import__('matplotlib.text', fromlist=[''])

entonces i se referirá a matplotlib.text.

En Python 3.1 o posterior, puede usar importlib:

import importlib

i = importlib.import_module("matplotlib.text")

Algunas notas

  • Si está tratando de importar algo desde una subcarpeta, por ejemplo ./feature/email.pyel código se verá como importlib.import_module("feature.email")

  • Antes de Python 3.3, no podía importar nada si no había __init__.py en la carpeta con el archivo que intentaba importar (vea las advertencias antes de decidir si desea mantener el archivo para compatibilidad con versiones anteriores, por ejemplo, con pytest).

  • importlib debería estar disponible en pypi para

    – Jeffrey José

    19 de agosto de 2012 a las 12:12

  • Para cualquiera que venga aquí desde Google. Cabe señalar que si está tratando de importar algo de una subcarpeta (por ejemplo, ./feature/email.py) el código se verá como importlib.import_module("feature.email")

    – Seanny123

    6 de diciembre de 2013 a las 7:13

  • Por último, recuerda también que no puedes importar nada si no hay __init__.py en la carpeta con el archivo que está intentando importar.

    – Seanny123

    6 de diciembre de 2013 a las 7:31

  • @mzjn Esto es para import moduleName donde moduleName es una cadena. Qué tal si from moduleName import * ?

    – Nam G VU

    30 de mayo de 2017 a las 6:47

  • Acabo de encontrar la respuesta a mi pregunta aquí en caso de que alguien la necesite stackoverflow.com/a/31306598/248616

    – Nam G VU

    30 de mayo de 2017 a las 7:54

avatar de usuario de gecco
gecco

importlib.import_module es lo que buscas Devuelve el módulo importado.

import importlib

# equiv. of your `import matplotlib.text as text`
text = importlib.import_module('matplotlib.text')

A partir de entonces, puede acceder a cualquier cosa en el módulo como text.myclass, text.myfunctionetc.

  • Una alternativa es imp.load_source(..) utilizando el imp módulo como se sugiere en esta respuesta stackoverflow.com/questions/67631/…

    –Evgeni Sergeev

    8 de junio de 2014 a las 6:30

  • @gecco Esto es para import moduleName donde moduleName es una cadena. Qué tal si from moduleName import * ?

    – Nam G VU

    30 de mayo de 2017 a las 6:57

  • ¿Alguien descifra la pregunta de @NamGVU?

    – Piedra del artillero

    22 de marzo de 2022 a las 23:05

pasé algún tiempo tratando de importar módulos de una lista, y este es el hilo que me llevó la mayor parte del camino, pero no entendí el uso de ___import____ –

así que aquí se explica cómo importar un módulo desde una cadena y obtener el mismo comportamiento que solo importar. Y prueba/excepto el caso de error, también. 🙂

  pipmodules = ['pycurl', 'ansible', 'bad_module_no_beer']
  for module in pipmodules:
      try:
          # because we want to import using a variable, do it this way
          module_obj = __import__(module)
          # create a global object containging our module
          globals()[module] = module_obj
      except ImportError:
          sys.stderr.write("ERROR: missing python module: " + module + "\n")
          sys.exit(1)

y sí, para python 2.7> tiene otras opciones, pero para 2.6

Aparte de usar el importlib uno también puede usar exec método para importar un módulo desde una variable de cadena.

Aquí estoy mostrando un ejemplo de importar el combinations método de itertools paquete usando el exec método:

MODULES = [
    ['itertools','combinations'],
]

for ITEM in MODULES:
    import_str = "from {0} import {1}".format(ITEM[0],', '.join(str(i) for i in ITEM[1:]))
    exec(import_str)

ar = list(combinations([1, 2, 3, 4], 2))
for elements in ar:
    print(elements)

Producción:

(1, 2)
(1, 3)
(1, 4)
(2, 3)
(2, 4)
(3, 4)

También puedes usar exec función incorporada que ejecuta cualquier cadena como un código de Python.

In [1]: module="pandas"
   ...: function = 'DataFrame'
   ...: alias="DF"

In [2]: exec(f"from {module} import {function} as {alias}")

In [3]: DF
Out[3]: pandas.core.frame.DataFrame

Para mí, esta fue la forma más legible de resolver mi problema.

Avatar de usuario de Serge Stroobandt
serge stroobandt

Módulo de instalación automática e importación desde la lista

El siguiente script funciona bien tanto con submódulos como con pseudosubmódulos.

# PyPI imports
import pkg_resources, subprocess, sys

modules   = {'lxml.etree', 'pandas', 'screeninfo'}
required  = {m.split('.')[0] for m in modules}
installed = {pkg.key for pkg in pkg_resources.working_set}
missing   = required - installed

if missing:
    subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--upgrade', 'pip'])
    subprocess.check_call([sys.executable, '-m', 'pip', 'install', *missing])

for module in set.union(required, modules):
    globals()[module] = __import__(module)

Pruebas:

print(pandas.__version__)
print(lxml.etree.LXML_VERSION)

avatar de usuario de prossblad
Prosblad

Desarrollé estas 3 funciones útiles:

def loadModule(moduleName):
    module = None
    try:
        import sys
        del sys.modules[moduleName]
    except BaseException as err:
        pass
    try:
        import importlib
        module = importlib.import_module(moduleName)
    except BaseException as err:
        serr = str(err)
        print("Error to load the module '" + moduleName + "': " + serr)
    return module

def reloadModule(moduleName):
    module = loadModule(moduleName)
    moduleName, modulePath = str(module).replace("' from '", "||").replace("<module '", '').replace("'>", '').split("||")
    if (modulePath.endswith(".pyc")):
        import os
        os.remove(modulePath)
        module = loadModule(moduleName)
    return module

def getInstance(moduleName, param1, param2, param3):
    module = reloadModule(moduleName)
    instance = eval("module." + moduleName + "(param1, param2, param3)")
    return instance

Y cada vez que quiero recargar una nueva instancia, solo tengo que llamar a getInstance() de esta manera:

myInstance = getInstance("MyModule", myParam1, myParam2, myParam3)

Finalmente puedo llamar a todas las funciones dentro de la nueva Instancia:

myInstance.aFunction()

La única especificidad aquí es personalizar la lista de parámetros (param1, param2, param3) de su instancia.

¿Ha sido útil esta solución?