Que fait réellement __future__ import absolute_import?

164

J'ai répondu à une question concernant les importations absolues en Python, que je pensais comprendre en lisant le journal des modifications Python 2.5 et en accompagnant le PEP . Cependant, en installant Python 2.5 et en essayant de créer un exemple d'utilisation correcte from __future__ import absolute_import, je me rends compte que les choses ne sont pas si claires.

Directement à partir du journal des modifications lié ci-dessus, cette déclaration a résumé avec précision ma compréhension du changement d'importation absolu:

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

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

Cela définit un package nommé pkgcontenant les sous pkg.main- pkg.stringmodules et .

Considérez 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 cherchera d'abord dans le répertoire du package pour effectuer une importation relative, trouve pkg / string.py, importe le contenu de ce fichier en tant que pkg.stringmodule, et ce module est lié au nom "string"dans l' pkg.mainespace de noms du module.

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

$ ls -R
.:
pkg/

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

__init__.pyet string.pysont vides. main.pycontient le code suivant:

import string
print string.ascii_uppercase

Comme prévu, son exécution 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 changer importle comportement de l 'importation absolue en utilisant une from __future__ import absolute_importdirective. Ce comportement d'importation absolue deviendra le comportement par défaut dans une version future (probablement Python 2.7). Une fois que les importations absolues sont la valeur par défaut, import stringtrouvera toujours la version de la bibliothèque standard.

J'ai donc créé pkg/main2.py, identique main.pymais avec la future directive d'importation 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 carrément la déclaration qui import stringtrouvera toujours la version std-lib avec les importations absolues activées. De plus, malgré l'avertissement selon lequel les importations absolues sont programmées pour devenir le comportement "nouveau par défaut", j'ai rencontré le même problème en utilisant à la fois Python 2.7, avec ou sans la __future__directive:

$ 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' printinstruction 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é stringcontenant uniquement un vide __init__.py- et au lieu d'émettre les importations en provenance main.py, je cd« d pour pkget exécuter 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 sur la __future__directive et les importations absolues.

Il me semble que cela est facilement explicable par ce qui suit (cela provient de la documentation Python 2 mais cette déclaration reste inchangée dans la même documentation pour Python 3):

sys.path

(...)

Comme initialisé au démarrage du programme, le premier élément de cette liste, path[0]est le répertoire contenant le script qui a été utilisé pour appeler l'interpréteur 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 de l'entrée standard), path[0]est la chaîne vide, qui dirige Python vers les modules de recherche dans le répertoire courant en premier.

Alors qu'est-ce que je manque? Pourquoi la __future__déclaration ne fait-elle apparemment pas 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?

Alchimiste à deux bits
la source

Réponses:

104

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

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

python pkg/script.py

pkg/script.pyne ressemble pas à une partie d'un package en Python. En suivant les procédures normales, le pkgrépertoire est ajouté au chemin et tous les .pyfichiers du pkgrépertoire ressemblent à des modules de niveau supérieur. import stringtrouve pkg/string.pynon pas parce qu'il effectue une importation relative, mais parce qu'il pkg/string.pysemble être le module de niveau supérieur string. Le fait que ce ne soit pas le stringmodule de bibliothèque standard ne s'affiche pas.

Pour exécuter le fichier dans le cadre du pkgpackage, vous pouvez faire

python -m pkg.script

Dans ce cas, le pkgrépertoire ne sera pas ajouté au chemin. Cependant, le répertoire courant sera ajouté au chemin.

Vous pouvez également ajouter un passe-partout pour pkg/script.pyque Python le traite comme faisant partie du pkgpackage, même lorsqu'il est exécuté en tant que fichier:

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

Cependant, cela n'affectera pas sys.path. Vous aurez besoin d'une manipulation supplémentaire pour supprimer le pkgrépertoire du chemin, et si pkgle répertoire parent de n'est pas sur le chemin, vous devrez également le coller sur le chemin.

user2357112 prend en charge Monica
la source
2
OK, je veux dire, je comprends ça. C'est exactement le comportement que mon message décrit. Face à cela, cependant, deux questions: (1.) Si «ce n'est pas tout à fait vrai», pourquoi la documentation le dit-elle catégoriquement? et, (2.) Comment, alors, import stringsi vous l'observez accidentellement, au moins sans fouiller sys.modules. N'est-ce pas ce que l' from __future__ import absolute_importon entend empêcher? Qu'est ce que ça fait? (PS, je ne suis pas le downvoter.)
Two-Bit Alchemist
14
Oui, c'était moi (voter contre «pas utile», ce n'était pas pour «faux»). Il est clair dans la section du bas que le PO comprend comment sys.pathfonctionne, et la question réelle n'a pas du tout été abordée. Autrement dit, que fait from __future__ import absolute_importréellement?
wim
5
@ Two-BitAlchemist: 1) Le journal des modifications est vaguement rédigé et non normatif. 2) Vous arrêtez de l'observer. Même fouiller à travers sys.modulesne vous donnera pas le stringmodule de bibliothèque standard si vous l'avez observé avec votre propre module de niveau supérieur. from __future__ import absolute_importne vise pas à empêcher les modules de niveau supérieur d'observer les modules de niveau supérieur; il est censé empêcher les modules internes au paquet d'observer les modules de niveau supérieur. Si vous exécutez le fichier dans le cadre du pkgpackage, les fichiers internes du package cessent de s'afficher en tant que niveau supérieur.
user2357112 prend en charge Monica
@ Two-BitAlchemist: Réponse révisée. Cette version est-elle plus utile?
user2357112 prend en charge Monica
1
@storen: En supposant qu'il pkgexiste un package sur le chemin de recherche d'importation, cela devrait être python -m pkg.main. -ma besoin d'un nom de module, pas d'un chemin de fichier.
user2357112 prend en charge Monica le
44

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 le python2 pkg/main2.pycomportement est différent du lancement python2puis de l'importation pkg.main2(ce qui équivaut à utiliser le -mcommutateur).

Si vous souhaitez exécuter un sous-module d'un package, utilisez toujours le -mcommutateur qui empêche l'interpréteur de chaîner la sys.pathliste et gère correctement la sémantique du sous-module.

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

Bakuriu
la source
Donc, essentiellement, cela ne fonctionne que pour un cas étroit où vous avez évité le problème du "répertoire actuel"? Cela semble être une implémentation beaucoup plus faible que celle décrite par PEP 328 et le changelog 2.5. Pensez-vous que la documentation est inexacte?
Two-Bit Alchemist
@ Two-BitAlchemist En fait, ce que vous faites est le "cas étroit". Vous ne lancez qu'un seul fichier python à exécuter, mais cela peut déclencher des centaines d'importations. Les sous-modules d'un package ne doivent tout simplement pas être exécutés, c'est tout.
Bakuriu
pourquoi python2 pkg/main2.pya- t- il un comportement différent du lancement de python2 puis de l'importation de pkg.main2?
storen
1
@storen C'est parce que le comportement avec les importations relatives change. Lorsque vous lancez pkg/main2.pypython (version 2) ne le traite paspkg comme un package. Lors de son utilisation python2 -m pkg.main2ou de son importation, tenez compte du fait qu'il pkgs'agit d'un package.
Bakuriu