La __import__ de Python no funciona como se esperaba
-
03-07-2019 - |
Pregunta
Cuando se usa __import__
con un nombre de puntos, algo así como: somepackage.somemodule
, el módulo devuelto no es somemodule
, lo que sea devuelto parece estar mayormente vacío! ¿Qué está pasando aquí?
Solución
De los documentos de python en __import__
:
__import__( name[, globals[, locals[, fromlist[, level]]]])
...
Cuando la variable de nombre es de la forma package.module, normalmente, el paquete de nivel superior (el nombre hasta se devuelve el primer punto), no el módulo nombrado por su nombre. Sin embargo, cuando un se proporciona un argumento de la lista no vacía, se devuelve el módulo nombrado por nombre. Esto se hace por compatibilidad con el bytecode generado para el diferentes tipos de declaración de importación; cuando se utiliza " import spam.ham.eggs " ;, el se debe colocar el paquete de spam de nivel superior en el espacio de nombres de importación, pero cuando utilizando " from spam.ham import eggs " ;, el El subpaquete spam.ham debe usarse para Encuentra los huevos variables. Como un solución para este comportamiento, use getattr () para extraer el deseado componentes Por ejemplo, podrías defina el siguiente ayudante:
def my_import(name): mod = __import__(name) components = name.split('.') for comp in components[1:]: mod = getattr(mod, comp) return mod
Parafraseando:
Cuando pides somepackage.somemodule
, __import__
devuelve somepackage.__init__.py
, que a menudo está vacío.
Devolverá somemodule
si proporciona fromlist
(una lista de los nombres de variables dentro de somemodule
que desea, que en realidad no se devuelven)
También puede, como lo hice, usar la función que sugieren.
Nota: hice esta pregunta con la intención de responderla yo mismo. Había un gran error en mi código, y después de haberlo diagnosticado erróneamente, me tomó mucho tiempo resolverlo, así que pensé que ayudaría a la comunidad SO y publicaría el problema que encontré aquí.
Otros consejos
python 2.7 tiene importlib, las rutas punteadas se resuelven como se esperaba
import importlib
foo = importlib.import_module('a.dotted.path')
instance = foo.SomeClass()
Hay una solución más simple, como se explica en la documentación:
Si simplemente desea importar un módulo (potencialmente dentro de un paquete) por su nombre, puede llamar a __import __ () y luego buscarlo en sys.modules:
>>> import sys
>>> name = 'foo.bar.baz'
>>> __import__(name)
<module 'foo' from ...>
>>> baz = sys.modules[name]
>>> baz
<module 'foo.bar.baz' from ...>
Hay algo que funciona como lo deseas: twisted.python.reflect.namedAny
:
>>> from twisted.python.reflect import namedAny
>>> namedAny("operator.eq")
<built-in function eq>
>>> namedAny("pysqlite2.dbapi2.connect")
<built-in function connect>
>>> namedAny("os")
<module 'os' from '/usr/lib/python2.5/os.pyc'>
Para python 2.6, escribí este fragmento:
def import_and_get_mod(str, parent_mod=None):
"""Attempts to import the supplied string as a module.
Returns the module that was imported."""
mods = str.split('.')
child_mod_str = '.'.join(mods[1:])
if parent_mod is None:
if len(mods) > 1:
#First time this function is called; import the module
#__import__() will only return the top level module
return import_and_get_mod(child_mod_str, __import__(str))
else:
return __import__(str)
else:
mod = getattr(parent_mod, mods[0])
if len(mods) > 1:
#We're not yet at the intended module; drill down
return import_and_get_mod(child_mod_str, mod)
else:
return mod
La forma en que lo hice es
foo = __import__('foo', globals(), locals(), ["bar"], -1)
foobar = eval("foo.bar")
entonces puedo acceder a cualquier contenido desde
foobar.functionName()