¿Qué hace from __future__ import absolute_import realmente?

10 minutos de lectura

avatar de usuario
Alquimista de dos bits

Respondí una pregunta sobre importaciones absolutas en Python, que pensé que entendía basándome en la lectura el registro de cambios de Python 2.5 y acompañando ENERGÍA. Sin embargo, al instalar Python 2.5 e intentar crear un ejemplo del uso adecuado from __future__ import absolute_importme doy cuenta de que las cosas no están tan claras.

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

Digamos que tiene un directorio de paquetes como este:

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

Esto define un paquete llamado pkg que contiene el pkg.main y pkg.string submódulos.

Considere el código en el módulo main.py. ¿Qué sucede si ejecuta la sentencia? import string? En Python 2.4 y 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 el pkg.string módulo, y ese módulo está vinculado al nombre "string" en el pkg.main espacio de nombres del módulo.

Así que creé esta estructura de directorios exacta:

$ ls -R
.:
pkg/

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

__init__.py y string.py están vacíos. main.py contiene el siguiente código:

import string
print string.ascii_uppercase

Como era de esperar, 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 de 2.5, encontramos esto (énfasis añadido):

En Python 2.5, puede cambiar importel comportamiento de las importaciones absolutas usando un from __future__ import absolute_import directiva. 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 string siempre encontrará la versión de la biblioteca estándar.

así creé pkg/main2.pyidéntico a main.py pero 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 rotundamente la afirmación de que import string voluntad siempre encuentre la versión std-lib con importaciones absolutas habilitadas. Además, a pesar de la advertencia de que las importaciones absolutas están programadas para convertirse en el “nuevo comportamiento predeterminado”, me encontré con este mismo problema al usar Python 2.7, con o sin el __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 el print declaración se cambia 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 vez de string.pyhe creado un módulo vacío, un directorio llamado string que contiene sólo un vacío __init__.py — y en lugar de emitir importaciones desde main.pyTengo cdTendría que pkg y ejecutar importaciones 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 el __future__ directiva e importaciones absolutas.

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

sys.ruta

(…)

Como se inicializó al iniciar el programa, el primer elemento de esta lista, path[0], es el directorio que contiene el script que se usó 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 módulos en el directorio actual primero.

Entonces, ¿qué me estoy perdiendo? ¿Por qué el __future__ la 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?

avatar de usuario
usuario2357112

El registro de cambios está mal redactado. from __future__ import absolute_import no le importa si algo es parte de la biblioteca estándar, y import string no siempre le dará el módulo de biblioteca estándar con importaciones absolutas activadas.

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

python pkg/script.py

pkg/script.py no parece parte de un paquete para Python. Siguiendo los procedimientos normales, el pkg se agrega el directorio a la ruta, y todo .py archivos en el pkg El directorio parece módulos de nivel superior. import string encuentra pkg/string.py no porque esté haciendo una importación relativa, sino porque pkg/string.py parece ser el módulo de nivel superior string. El hecho de que esta no sea la biblioteca estándar string no me sale el modulo

Para ejecutar el archivo como parte del pkg paquete, usted podría hacer

python -m pkg.script

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

También puede agregar algunos repetitivos a pkg/script.py para hacer que Python lo trate como parte del pkg paquete 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 pkg directorio de la ruta, y si pkgEl directorio principal de no está en la ruta, también deberá pegarlo en la ruta.

  • OK, quiero decir, lo entiendo. 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 string si accidentalmente lo sombreas, al menos sin revolver sys.modules. ¿No es esto lo que from __future__ import absolute_import se pretende prevenir? ¿Qué hace? (PD, no soy el votante negativo).

    – Alquimista de dos bits

    16 de noviembre de 2015 a las 21:29


  • Sí, ese fui yo (voto negativo por ‘no útil’, no fue por ‘incorrecto’). Está claro desde la sección inferior que el OP entiende cómo sys.path funciona, y la pregunta real no se ha abordado en absoluto. Eso es, Que hace from __future__ import absolute_import en realidad hacer?

    – Wim

    16 de noviembre de 2015 a las 21:34


  • @Two-BitAlchemist: 1) El registro de cambios está redactado de forma vaga y no es normativo. 2) Dejas de seguirlo. Incluso rebuscando sys.modules no obtendrá la biblioteca estándar string módulo si lo sombreó con su propio módulo de nivel superior. from __future__ import absolute_import no tiene la intención de evitar que los módulos de nivel superior sigan a los módulos de nivel superior; se supone que debe evitar que los módulos internos del paquete sombreen los módulos de nivel superior. Si ejecuta el archivo como parte del pkg paquete, los archivos internos del paquete dejan de aparecer como de nivel superior.

    – usuario2357112

    16/11/2015 a las 21:35

  • @Two-BitAlchemist: Respuesta revisada. ¿Es esta versión más útil?

    – usuario2357112

    16 de noviembre de 2015 a las 21:56

  • @storen: Suponiendo pkg es un paquete en la ruta de búsqueda de importación, que debe ser python -m pkg.main. -m necesita un nombre de módulo, no una ruta de archivo.

    – usuario2357112

    11 de abril de 2016 a las 14:58

avatar de usuario
Bakuriú

La diferencia entre 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.py tiene un comportamiento diferente a continuación, el lanzamiento python2 y luego importando pkg.main2 (que es equivalente a usar el -m cambiar).

Si alguna vez desea ejecutar un submódulo de un paquete, siempre use el -m interruptor que evita que el intérprete encadene el sys.path list y maneja correctamente la semántica del submódulo.

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

  • Entonces, ¿esencialmente solo funciona para un caso limitado en el que ha 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. ¿Cree que la documentación es inexacta?

    – Alquimista de dos bits

    16/11/2015 a las 21:31

  • @Two-BitAlchemist En realidad qué están haciendo es el “caso estrecho”. Ejecuta solo un único archivo python para que se ejecute, pero esto puede desencadenar cientos de importaciones. Los submódulos de un paquete simplemente no deberían ejecutarse, eso es todo.

    – Bakuriu

    16 de noviembre de 2015 a las 22:02

  • por qué python2 pkg/main2.py ¿Tiene un comportamiento diferente al iniciar python2 y luego importar pkg.main2?

    – almacenar

    11 de abril de 2016 a las 12:04

  • @storen Eso es porque cambia el comportamiento con importaciones relativas. cuando lanzas pkg/main2.py python (versión 2) hace no tratar pkg como paquete Durante el uso python2 -m pkg.main2 o importarlo hacer toma en cuenta que pkg es un paquete

    – Bakuriu

    11/04/2016 a las 18:35

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad