from - python__init__.py best practices
Importa en__init__.py y la instrucción `import as` (4)
Me he encontrado con un problema con tener importaciones en __init__.py
y usar import as
con importaciones absolutas en los módulos del paquete.
Mi proyecto tiene un subpaquete y en su __init__.py
"levanto" una de las clases de un módulo al nivel de subpaquete con from import as
declaración. El módulo importa otros módulos de ese subpaquete con importaciones absolutas. Recibo este error AttributeError: ''module'' object has no attribute ''subpkg''
.
Ejemplo
Estructura :
pkg/
├── __init__.py
├── subpkg
│ ├── __init__.py
│ ├── one.py
│ └── two_longname.py
└── tst.py
pkg / __ init__.py está vacío.
pkg / subpkg / __ init__.py :
from pkg.subpkg.one import One
pkg / subpkg / one.py :
import pkg.subpkg.two_longname as two
class One(two.Two):
pass
pkg / subpkg / two_longname.py :
class Two:
pass
pkg / tst.py :
from pkg.subpkg import One
print(One)
Salida :
$ python3.4 -m pkg.tst
Traceback (most recent call last):
File "/usr/lib/python3.4/runpy.py", line 170, in _run_module_as_main
"__main__", mod_spec)
File "/usr/lib/python3.4/runpy.py", line 85, in _run_code
exec(code, run_globals)
File "/home/and/dev/test/python/imptest2/pkg/tst.py", line 1, in <module>
from pkg.subpkg import One
File "/home/and/dev/test/python/imptest2/pkg/subpkg/__init__.py", line 1, in <module>
from pkg.subpkg.one import One
File "/home/and/dev/test/python/imptest2/pkg/subpkg/one.py", line 1, in <module>
import pkg.subpkg.two_longname as two
AttributeError: ''module'' object has no attribute ''subpkg''
Soluciones
Hay cambios que lo hacen funcionar:
Vacíe
pkg/subpkg/__init__.py
e importe directamente desdepkg.subpkg.one
.No considero esto como una opción porque AFAIK "elevar" las cosas al nivel del paquete está bien. Aquí está la cita de un artículo :
Una cosa común a hacer en su init .py es importar las Clases, funciones, etc. seleccionadas al nivel del paquete para que puedan ser importadas convenientemente desde el paquete.
Cambiando la
import as
from import
enone.py
:from pkg.subpkg import two_longname class One(two_longname.Two): pass
La única desventaja aquí es que no puedo crear un alias corto para el módulo. Tengo la idea de la respuesta de @ begueradj.
También es posible usar una importación relativa en one.py
para solucionar el problema. Pero creo que es solo una variación de la solución # 2.
Preguntas
¿Puede alguien explicar lo que realmente está pasando aquí? ¿Por qué una combinación de importaciones en
__init__.py
y el uso de laimport as
conducen a tales problemas?¿Hay mejores soluciones?
Ejemplo original
Este es mi ejemplo original. No es muy realista, pero no lo estoy eliminando, por lo que la respuesta de @ begueradj todavía tiene sentido.
pkg / __ init__.py está vacío.
pkg / subpkg / __ init__.py :
from pkg.subpkg.one import ONE
pkg / subpkg / one.py :
import pkg.subpkg.two
ONE = pkg.subpkg.two.TWO
pkg / subpkg / two.py :
TWO = 2
pkg / tst.py :
from pkg.subpkg import ONE
Salida :
$ python3.4 -m pkg.tst
Traceback (most recent call last):
File "/usr/lib/python3.4/runpy.py", line 170, in _run_module_as_main
"__main__", mod_spec)
File "/usr/lib/python3.4/runpy.py", line 85, in _run_code
exec(code, run_globals)
File "/home/and/dev/test/python/imptest/pkg/tst.py", line 1, in <module>
from pkg.subpkg import ONE
File "/home/and/dev/test/python/imptest/pkg/subpkg/__init__.py", line 2, in <module>
from pkg.subpkg.one import ONE
File "/home/and/dev/test/python/imptest/pkg/subpkg/one.py", line 6, in <module>
ONE = pkg.subpkg.two.TWO
AttributeError: ''module'' object has no attribute ''subpkg''
Inicialmente tuve esto en one.py :
import pkg.subpkg.two as two
ONE = two.TWO
En ese caso, obtengo un error en la importación (al igual que en mi proyecto original que también usa la import as
).
Aquí hay una teoría sobre lo que está pasando.
Cuando usas la palabra reservada, por ejemplo:
import pkg.subpkg.two_longname as two
Python debe inicializar y resolver completamente todas las dependencias que tienen que ver con pkg.subpkg
. Pero hay un problema, para cargar completamente el subpkg
también necesitas cargar one.py
, ¿verdad? que al mismo tiempo importa two_longname.py
usando la palabra clave as
... ¿Puedes ver la recursión aquí? Por eso a la hora de hacer:
import pkg.subpkg.two_longname as two
subpkg
un error al afirmar que el subpkg
no existe.
Para realizar una prueba, vaya a one.py y cámbielo a esto:
#import pkg.subpkg.two_longname as two
from pkg.subpkg import two_longname
#class One(two.Two):
class One(two_longname.Two):
pass
Supongo que todo esto tiene que ver con el rendimiento, Python carga un módulo parcialmente cuando es posible. Y la palabra clave as
es una de las excepciones. No sé si hay otros, pero sería interesante saber sobre ellos.
Como la respuesta aceptada indica que esto es un problema con el comportamiento de Python.
He archivado un error: http://bugs.python.org/issue30024
La solución de Serhiy Storchaka se fusionó y se esperaba en Python 3.7
La estructura de su proyecto con respecto a la forma en que llama a los módulos, debe ser así:
pkg/
├── __init__.py
├── subpkg
│ ├── __init__.py
│ ├── one.py
│ └── two.py
tst.py
Define tu two.py así:
class TWO:
def functionTwo(self):
print("2")
Define tu one.py así:
from pkg.subpkg import two
class ONE:
def functionOne(self):
print("1")
self.T=two.TWO()
print("Calling TWO from ONE: ")
self.T.functionTwo()
Define tu test.py asi
from pkg.subpkg import one
class TEST:
def functionTest(self):
O=one.ONE()
O.functionOne()
if __name__==''__main__'':
T=TEST()
T.functionTest()
Cuando ejecutes, obtendrás esto:
1
Calling TWO from ONE:
2
Espero que esto ayude.
Usted asume incorrectamente que uno no puede tener un alias con from ... import
, from ... import ... as
ha estado allí desde Python 2.0. La import ... as
es la sintaxis oscura que no muchos conocen, pero que usas por accidente en tu código.
PEP 0221 afirma que los 2 siguientes son "efectivamente" iguales:
-
import foo.bar.bazaar as baz
-
from foo.bar import bazaar as baz
La declaración no es del todo cierta, como lo demuestra el caso de esquina que conoció, es decir, si los módulos necesarios ya existen en sys.modules
pero aún no están inicializados. La import ... as
requiere que el módulo foo.bar
se inyecte en el espacio de nombres de foo
como la bar
atributos, además de estar en sys.modules
, mientras que el from ... import ... as
busca foo.bar
en sys.modules
.
(Tenga en cuenta también que import foo.bar
solo garantiza que el módulo foo.bar
esté en sys.modules
y que se sys.modules
acceder a él como foo.bar
, pero es posible que aún no se haya inicializado por completo).
Cambiar el código de la siguiente manera hizo el truco para mí:
# import pkg.subpkg.two_longname as two
from pkg.subpkg import two_longname as two
Y el código se ejecuta perfectamente tanto en Python 2 como en Python 3.
Además, en one.py
no puedes hacerlo from pkg import subpkg
, por la misma razón.
Para demostrar este error aún más, arregle su one.py
como se one.py
arriba, y agregue el siguiente código en tst.py
:
import pkg
import pkg.subpkg.two_longname as two
del pkg.subpkg
from pkg.subpkg import two_longname as two
import pkg.subpkg.two_longname as two
Solo la última línea se bloquea, porque from ... import
consulta los sys.modules
para pkg.subpkg
y lo encuentra allí, mientras que import ... as
consultes sys.modules
para pkg
y trata de encontrar subpkg
como un atributo en el módulo pkg
. Como acabamos de eliminar ese atributo, la última línea falla con AttributeError: ''module'' object has no attribute ''subpkg''
.
Como la sintaxis import foo.bar as baz
es un poco oscura y agrega más casos en las esquinas, y rara vez, si alguna vez la he visto, se usa, recomendaría evitarla completamente y favorecerla from .. import ... as
.