top practices from beyond best attempted __init__ __all__ python python-3.x

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:

  1. Vacíe pkg/subpkg/__init__.py e importe directamente desde pkg.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.

  2. Cambiando la import as from import en one.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

  1. ¿Puede alguien explicar lo que realmente está pasando aquí? ¿Por qué una combinación de importaciones en __init__.py y el uso de la import as conducen a tales problemas?

  2. ¿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:

  1. import foo.bar.bazaar as baz
  2. 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 .