¿Qué hace realmente __future__ import absolute_import?

163

He respondido a una pregunta con respecto a las importaciones absolutas en Python, que pensé que comprendía basado en la lectura de la lista de cambios Python 2.5 y acompañando PEP . Sin embargo, al instalar Python 2.5 e intentar crear un ejemplo de uso adecuado from __future__ import absolute_import, me doy cuenta de que las cosas no están tan claras.

Directamente desde el registro de cambios vinculado anteriormente, esta declaración resumió con precisión mi comprensión del cambio absoluto de importación:

Digamos que tiene un directorio de paquetes como este:

pkg/
pkg/__init__.py
pkg/main.py
pkg/string.py

Esto define un paquete llamado que pkgcontiene los submódulos pkg.mainy pkg.string.

Considere el código en el módulo main.py. ¿Qué sucede si ejecuta la declaración import string? En Python 2.4 y versiones anteriores, primero buscará en el directorio del paquete para realizar una importación relativa, encuentra pkg / string.py, importa el contenido de ese archivo como pkg.stringmódulo y ese módulo está vinculado al nombre "string"en el pkg.mainespacio de nombres del módulo.

Entonces creé esta estructura de directorio exacta:

$ ls -R
.:
pkg/

./pkg:
__init__.py  main.py  string.py

__init__.pyy string.pyestan vacios main.pycontiene el siguiente código:

import string
print string.ascii_uppercase

Como se esperaba, ejecutar esto con Python 2.5 falla con un AttributeError:

$ python2.5 pkg/main.py
Traceback (most recent call last):
  File "pkg/main.py", line 2, in <module>
    print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

Sin embargo, más adelante en el registro de cambios 2.5, encontramos esto (énfasis agregado):

En Python 2.5, puede cambiar importel comportamiento de las importaciones absolutas utilizando una from __future__ import absolute_importdirectiva. Este comportamiento de importación absoluta se convertirá en el predeterminado en una versión futura (probablemente Python 2.7). Una vez que las importaciones absolutas son las predeterminadas, import stringsiempre encontrará la versión de la biblioteca estándar.

Así creé pkg/main2.py, idéntico main.pypero con la futura directiva de importación adicional. Ahora se ve así:

from __future__ import absolute_import
import string
print string.ascii_uppercase

Ejecutar esto con Python 2.5, sin embargo ... falla con un AttributeError:

$ python2.5 pkg/main2.py
Traceback (most recent call last):
  File "pkg/main2.py", line 3, in <module>
    print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

Esto contradice la afirmación bastante rotundamente que import stringserá siempre encontrar la versión STD-lib con las importaciones absolutos habilitadas. Además, a pesar de la advertencia de que las importaciones absolutas están programadas para convertirse en el comportamiento "nuevo predeterminado", me encontré con este mismo problema usando Python 2.7, con o sin la __future__directiva:

$ python2.7 pkg/main.py
Traceback (most recent call last):
  File "pkg/main.py", line 2, in <module>
    print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

$ python2.7 pkg/main2.py
Traceback (most recent call last):
  File "pkg/main2.py", line 3, in <module>
    print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

así como Python 3.5, con o sin (suponiendo que la printdeclaración se cambie en ambos archivos):

$ python3.5 pkg/main.py
Traceback (most recent call last):
  File "pkg/main.py", line 2, in <module>
    print(string.ascii_uppercase)
AttributeError: module 'string' has no attribute 'ascii_uppercase'

$ python3.5 pkg/main2.py
Traceback (most recent call last):
  File "pkg/main2.py", line 3, in <module>
    print(string.ascii_uppercase)
AttributeError: module 'string' has no attribute 'ascii_uppercase'

He probado otras variaciones de esto. En lugar de string.py, he creado un módulo vacío - un directorio llamado stringque contiene sólo un vacío __init__.py- y en lugar de la emisión de las importaciones de main.py, tengo cd'd a pkge importaciones ejecutar directamente desde el REPL. Ninguna de estas variaciones (ni una combinación de ellas) cambió los resultados anteriores. No puedo conciliar esto con lo que he leído sobre la __future__directiva y las importaciones absolutas.

Me parece que esto es fácilmente explicable por lo siguiente (esto es de los documentos de Python 2, pero esta declaración permanece sin cambios en los mismos documentos para Python 3):

sys.path

(...)

Como se inicializó al iniciar el programa, el primer elemento de esta lista path[0]es el directorio que contiene el script que se utilizó para invocar al intérprete de Python. Si el directorio del script no está disponible (por ejemplo, si el intérprete se invoca de forma interactiva o si el script se lee desde la entrada estándar), path[0]es la cadena vacía, que dirige a Python a buscar los módulos en el directorio actual primero.

Entonces, ¿qué me estoy perdiendo? ¿Por qué la __future__declaración aparentemente no hace lo que dice, y cuál es la resolución de esta contradicción entre estas dos secciones de documentación, así como entre el comportamiento descrito y el real?

Alquimista de dos bits
fuente

Respuestas:

104

El registro de cambios está redactado descuidadamente. from __future__ import absolute_importno le importa si algo es parte de la biblioteca estándar, y import stringno siempre le dará el módulo de biblioteca estándar con importaciones absolutas.

from __future__ import absolute_importsignifica que si usted import string, Python siempre buscará un stringmódulo de nivel superior , en lugar de hacerlo current_package.string. Sin embargo, no afecta la lógica que Python usa para decidir qué archivo es el stringmódulo. Cuando tu lo hagas

python pkg/script.py

pkg/script.pyno parece parte de un paquete para Python. Siguiendo los procedimientos normales, el pkgdirectorio se agrega a la ruta y todos los .pyarchivos en el pkgdirectorio parecen módulos de nivel superior. import stringencuentra pkg/string.pyno porque está haciendo una importación relativa, sino porque pkg/string.pyparece ser el módulo de nivel superior string. El hecho de que este no es el stringmódulo de la biblioteca estándar no surge.

Para ejecutar el archivo como parte del pkgpaquete, puede hacer

python -m pkg.script

En este caso, el pkgdirectorio no se agregará a la ruta. Sin embargo, el directorio actual se agregará a la ruta.

También puede agregar algo de repetitivo para pkg/script.pyque Python lo trate como parte del pkgpaquete incluso cuando se ejecuta como un archivo:

if __name__ == '__main__' and __package__ is None:
    __package__ = 'pkg'

Sin embargo, esto no afectará sys.path. Necesitará un manejo adicional para eliminar el pkgdirectorio de la ruta, y si pkgel directorio principal no está en la ruta, también deberá pegarlo en la ruta.

user2357112 es compatible con Monica
fuente
2
OK, quiero decir, entiendo eso. Ese es exactamente el comportamiento que documenta mi publicación. Sin embargo, frente a eso, dos preguntas: (1.) Si "eso no es exactamente cierto", ¿por qué los documentos dicen categóricamente que lo es? y, (2.) ¿Cómo, entonces, import stringsi lo sombreas accidentalmente, al menos sin rebuscar sys.modules? ¿No es esto lo que from __future__ import absolute_importse pretende prevenir? ¿Qué hace? (PD, no soy el votante negativo.)
Alquimista de dos bits el
14
Sí, ese era yo (voto negativo por "no útil", no por "incorrecto"). Está claro en la sección inferior que el OP entiende cómo sys.pathfunciona, y la pregunta real no se ha abordado en absoluto. Es decir, ¿qué hace from __future__ import absolute_importrealmente?
wim
55
@ Two-BitAlchemist: 1) El registro de cambios está redactado de forma flexible y no es normativo. 2) Dejas de seguirlo. Incluso rebuscar sys.modulesno te dará el stringmódulo de biblioteca estándar si lo sombreaste con tu propio módulo de nivel superior. from __future__ import absolute_importno está destinado a evitar que los módulos de nivel superior sombreen los módulos de nivel superior; se supone que evita que los módulos internos del paquete sombreen los módulos de nivel superior. Si ejecuta el archivo como parte del pkgpaquete, los archivos internos del paquete dejarán de aparecer como de nivel superior.
user2357112 es compatible con Monica el
@ Two-BitAlchemist: Respuesta revisada. ¿Es esta versión más útil?
user2357112 es compatible con Monica el
1
@storen: Suponiendo que pkges un paquete en la ruta de búsqueda de importación, eso debería ser python -m pkg.main. -mnecesita un nombre de módulo, no una ruta de archivo.
user2357112 es compatible con Monica el
44

La diferencia entre las importaciones absolutas y relativas entra en juego solo cuando importa un módulo de un paquete y ese módulo importa otro submódulo de ese paquete. Ver la diferencia:

$ mkdir pkg
$ touch pkg/__init__.py
$ touch pkg/string.py
$ echo 'import string;print(string.ascii_uppercase)' > pkg/main1.py
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pkg/main1.py", line 1, in <module>
    import string;print(string.ascii_uppercase)
AttributeError: 'module' object has no attribute 'ascii_uppercase'
>>> 
$ echo 'from __future__ import absolute_import;import string;print(string.ascii_uppercase)' > pkg/main2.py
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ
>>> 

En particular:

$ python2 pkg/main2.py
Traceback (most recent call last):
  File "pkg/main2.py", line 1, in <module>
    from __future__ import absolute_import;import string;print(string.ascii_uppercase)
AttributeError: 'module' object has no attribute 'ascii_uppercase'
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ
>>> 
$ python2 -m pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ

Tenga en cuenta que python2 pkg/main2.pytiene un comportamiento diferente al iniciar python2y luego importar pkg.main2(que es equivalente a usar el -mconmutador).

Si alguna vez desea ejecutar un submódulo de un paquete, utilice siempre el -mmodificador que impide que el intérprete encadene la sys.pathlista y maneje correctamente la semántica del submódulo.

Además, prefiero usar importaciones relativas explícitas para submódulos de paquetes, ya que proporcionan más semántica y mejores mensajes de error en caso de falla.

Bakuriu
fuente
¿Entonces esencialmente solo funciona para un caso estrecho donde has evitado el problema del "directorio actual"? Esa parece ser una implementación mucho más débil que la descrita por PEP 328 y el registro de cambios 2.5. ¿Crees que la documentación es inexacta?
Alquimista de dos bits el
@ Two-BitAlchemist En realidad, lo que está haciendo es el "caso estrecho". Solo ejecuta un único archivo de Python para ejecutar, pero esto puede desencadenar cientos de importaciones. Los submódulos de un paquete simplemente no deberían ejecutarse, eso es todo.
Bakuriu
¿Por qué python2 pkg/main2.pytiene un comportamiento diferente al iniciar python2 y luego importar pkg.main2?
storen
1
@storen Eso se debe a que el comportamiento con las importaciones relativas cambia. Cuando ejecutas pkg/main2.pypython (versión 2) no se trata pkgcomo un paquete. Si bien la utilización python2 -m pkg.main2o la importación se haga tener en cuenta que pkges un paquete.
Bakuriu