web-dev-qa-db-fra.com

Que fait réellement de __future__ import absolute_import?

J'ai répond une question concernant les importations absolues en Python, ce que je pensais comprendre en fonction de la lecture le Python 2.5 changelog et l'accompagnement PEP . Cependant, lors de l'installation de Python 2.5 et de la tentative de création d'un exemple d'utilisation correcte de from __future__ import absolute_import, je me rends compte que les choses ne sont pas aussi claires.

Directement du changelog lié ci-dessus, cette déclaration résume avec précision ma compréhension du changement absolu d'importation:

Disons que vous avez un répertoire de paquets comme celui-ci:

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

Ceci définit un package nommé pkg contenant les sous-modules pkg.main et pkg.string.

Considérons le code dans le module main.py. Que se passe-t-il s'il exécute l'instruction import string? Dans Python 2.4 et versions antérieures, il commence par rechercher dans le répertoire du package une importation relative, trouve pkg/string.py, importe le contenu de ce fichier sous le module pkg.string et ce module est lié à le nom "string" dans l'espace de noms du module pkg.main.

J'ai donc créé cette structure de répertoire exacte:

$ ls -R
.:
pkg/

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

__init__.py et string.py sont vides. main.py contient le code suivant:

import string
print string.ascii_uppercase

Comme prévu, exécuter ceci avec Python 2.5 échoue avec 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'

Cependant, plus loin dans le changelog 2.5, nous trouvons ceci (emphase ajoutée):

Dans Python 2.5, vous pouvez passer le comportement de import aux importations absolues à l'aide de la directive from __future__ import absolute_import. Ce comportement d’importation absolue deviendra le comportement par défaut dans une version ultérieure (probablement Python 2.7). Une fois que les importations absolues sont la valeur par défaut, import string trouvera toujours la version de la bibliothèque standard.

J'ai ainsi créé pkg/main2.py, identique à main.py mais avec la directive d'importation future supplémentaire. Cela ressemble maintenant à ceci:

from __future__ import absolute_import
import string
print string.ascii_uppercase

L'exécution de ceci avec Python 2.5, cependant ... échoue avec 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'

Cela contredit catégoriquement l'affirmation selon laquelle import string cherchera toujours la version std-lib avec les importations absolues activées. De plus, malgré l'avertissement selon lequel les importations absolues sont planifiées pour devenir le comportement de "nouvelle valeur par défaut", j'ai rencontré le même problème en utilisant à la fois Python 2.7, avec ou sans la directive __future__:

$ 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'

ainsi que Python 3.5, avec ou sans (en supposant que l'instruction print soit modifiée dans les deux fichiers):

$ 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'

J'ai testé d'autres variantes de cela. Au lieu de string.py, j'ai créé un module vide - un répertoire nommé string contenant uniquement un __init__.py vide - et au lieu d'émettre des importations à partir de main.py, j'ai cd 'd à pkg et lancez les importations directement à partir du REPL. Aucune de ces variations (ni une combinaison d'entre elles) n'a changé les résultats ci-dessus. Je ne peux pas concilier cela avec ce que j'ai lu à propos de la directive __future__ et des imports absolus.

Il me semble que cela s’explique facilement par le suivant (cela provient des docs Python 2, mais cette instruction reste inchangée dans les mêmes docs que Python 3):

sys.path

(...)

Initialisé au démarrage du programme, le premier élément de cette liste, path[0], est le répertoire contenant le script utilisé pour appeler l'interprète Python. Si le répertoire de script n'est pas disponible (par exemple si l'interpréteur est appelé de manière interactive ou si le script est lu à partir d'une entrée standard), path[0] est la chaîne vide qui dirige Python pour commencer par rechercher les modules dans le répertoire courant.

Alors qu'est-ce qui me manque? Pourquoi l'instruction __future__ ne semble-t-elle pas faire ce qu'elle dit et quelle est la résolution de cette contradiction entre ces deux sections de la documentation, ainsi qu'entre le comportement décrit et le comportement réel?

129
Two-Bit Alchemist

Le journal des modifications est mal rédigé. from __future__ import absolute_import ne se soucie pas de savoir si quelque chose fait partie de la bibliothèque standard et import string ne vous donnera pas toujours le module de bibliothèque standard avec les importations absolues.

from __future__ import absolute_import signifie que si vous import string, Python recherchera toujours un module _ de niveau supérieur string, plutôt que current_package.string. Cependant, cela n'affecte pas la logique utilisée par Python pour décider quel fichier est le module string. Quand tu fais

python pkg/script.py

pkg/script.py ne ressemble pas à un paquet pour Python. Après les procédures normales, le répertoire pkg est ajouté au chemin et tous les fichiers .py du répertoire pkg ressemblent à des modules de niveau supérieur. import string trouve pkg/string.py non pas parce qu'il s'agit d'une importation relative, mais parce que pkg/string.py semble être le module de niveau supérieur string. Le fait que ce ne soit pas le module standard string de la bibliothèque standard ne se présente pas.

Pour exécuter le fichier dans le package pkg, vous pouvez effectuer les opérations suivantes:

python -m pkg.script

Dans ce cas, le répertoire pkg ne sera pas ajouté au chemin. Cependant, le répertoire en cours sera ajouté au chemin.

Vous pouvez également ajouter du code d'exécution à pkg/script.py pour que Python le traite comme faisant partie du package pkg même s'il est exécuté en tant que fichier:

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

Cependant, cela n'affectera pas sys.path. Vous aurez besoin de quelques manipulations supplémentaires pour supprimer le répertoire pkg du chemin, et si le répertoire parent de pkg ne se trouve pas sur le chemin, vous devrez également le coller sur celui-ci.

83
user2357112

La différence entre les importations absolues et relatives n'entre en jeu que lorsque vous importez un module à partir d'un package et que ce module importe un autre sous-module à partir de ce package. Regarde la différence:

$ 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 particulier:

$ 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

Notez que python2 pkg/main2.py a un comportement différent en lançant python2, puis en important pkg.main2 (ce qui revient à utiliser le commutateur -m.).

Si vous souhaitez exécuter un sous-module d'un package, utilisez toujours le commutateur -m qui empêche l'interpréteur de modifier la liste sys.path et gère correctement la sémantique du sous-module.

De plus, je préfère de loin utiliser les importations relatives explicites pour les sous-modules de paquets car ils fournissent plus de sémantique et de meilleurs messages d'erreur en cas d'échec.

41
Bakuriu