Was macht eigentlich aus __future__ import absolute_import?

163

ich habe eine Frage zu absoluten Importen in Python beantwortet , die ich aufgrund des Lesens des Python 2.5-Änderungsprotokolls und des zugehörigen PEP zu verstehen glaubte . Bei der Installation von Python 2.5 und dem Versuch, ein Beispiel für die ordnungsgemäße Verwendung zu erstellen, stelle from __future__ import absolute_importich jedoch fest, dass die Dinge nicht so klar sind.

Direkt aus dem oben verlinkten Änderungsprotokoll fasste diese Aussage mein Verständnis der absoluten Importänderung genau zusammen:

Angenommen, Sie haben ein Paketverzeichnis wie das folgende:

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

Dies definiert ein Paket pkgmit dem Namen pkg.mainund den pkg.stringSubmodulen.

Betrachten Sie den Code im main.py-Modul. Was passiert, wenn die Anweisung ausgeführt wird?import string ? In Python 2.4 und früheren Versionen wird zunächst im Verzeichnis des Pakets nach einem relativen Import gesucht, pkg / string.py gefunden, der Inhalt dieser Datei als pkg.stringModul importiert und das Modul an den Namen "string"im pkg.mainNamespace des Moduls gebunden .

Also habe ich genau diese Verzeichnisstruktur erstellt:

$ ls -R
.:
pkg/

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

__init__.py und string.py sind leer. main.pyenthält den folgenden Code:

import string
print string.ascii_uppercase

Wie erwartet schlägt das Ausführen mit Python 2.5 mit einem fehl 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'

Weiter unten im 2.5-Änderungsprotokoll finden wir jedoch Folgendes (Hervorhebung hinzugefügt):

In Python 2.5 können Sie das importVerhalten mithilfe einer from __future__ import absolute_importDirektive auf absolute Importe umstellen . Dieses absolute Importverhalten wird in einer zukünftigen Version (wahrscheinlich Python 2.7) zum Standard. Sobald absolute Importe die Standardeinstellung sind,import string wird immer die Version der Standardbibliothek gefunden.

Ich habe also erstellt pkg/main2.py, identisch mit, main.pyaber mit der zusätzlichen zukünftigen Importrichtlinie. Es sieht jetzt so aus:

from __future__ import absolute_import
import string
print string.ascii_uppercase

Das Ausführen mit Python 2.5 schlägt jedoch fehl mit 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'

Dies steht im Widerspruch ziemlich flach die Aussage aus , import stringwird immer die std-lib - Version findet mit absoluten Importen aktiviert. Darüber hinaus habe ich trotz der Warnung, dass absolute Importe zum "neuen Standard" -Verhalten werden sollen, dasselbe Problem mit Python 2.7 mit oder ohne __future__Anweisung festgestellt:

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

sowie Python 3.5 mit oder ohne (vorausgesetzt, die printAnweisung wird in beiden Dateien geändert):

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

Ich habe andere Variationen davon getestet. Statt string.pyhabe ich ein leeres Modul erstellt - ein Verzeichnis mit dem Namen stringnur ein leeren enthalten __init__.py- und stattdessen die Einfuhr von Ausgabe aus main.py, ich habe cd‚dpkg und die Importe direkt aus dem REPL laufen. Weder diese Variationen (noch eine Kombination davon) haben die obigen Ergebnisse verändert. Ich kann dies nicht mit dem vereinbaren, was ich über die __future__Richtlinie und die absoluten Importe gelesen habe .

Es scheint mir, dass dies durch Folgendes leicht zu erklären ist (dies stammt aus den Python 2-Dokumenten, aber diese Aussage bleibt in denselben Dokumenten für Python 3 unverändert):

sys.path

(...)

Wie beim Programmstart initialisiert, ist das erste Element dieser Liste path[0]das Verzeichnis, das das Skript enthält, mit dem der Python-Interpreter aufgerufen wurde. Wenn das Skriptverzeichnis nicht verfügbar ist (z. B. wenn der Interpreter interaktiv aufgerufen wird oder wenn das Skript aus der Standardeingabe gelesen wird), path[0]ist dies die leere Zeichenfolge, die Python anweist, zuerst die Module im aktuellen Verzeichnis zu suchen.

Also, was vermisse ich? Warum tut die __future__Aussage scheinbar nicht das, was sie sagt, und wie lässt sich dieser Widerspruch zwischen diesen beiden Abschnitten der Dokumentation sowie zwischen beschriebenem und tatsächlichem Verhalten lösen?

Zwei-Bit-Alchemist
quelle

Antworten:

104

Das Changelog ist schlampig formuliert. from __future__ import absolute_importkümmert sich nicht darum, ob etwas Teil der Standardbibliothek ist, und gibt import stringIhnen nicht immer das Standardbibliotheksmodul mit absoluten Importen.

from __future__ import absolute_importWenn Sie dies import stringtun, sucht Python immer nach einem stringModul der obersten Ebene und nicht nach einem Modul der obersten Ebene current_package.string. Dies hat jedoch keinen Einfluss auf die Logik, mit der Python entscheidet, welche Datei das stringModul ist. Wenn Sie das tun

python pkg/script.py

pkg/script.pysieht für Python nicht wie ein Teil eines Pakets aus. Nach den normalen Verfahren wird das pkgVerzeichnis dem Pfad hinzugefügt, und alle .pyDateien im pkgVerzeichnis sehen aus wie Module der obersten Ebene. import stringfindet pkg/string.pynicht, weil es einen relativen Import macht, sondern weil pkg/string.pyes das Modul der obersten Ebene zu sein scheint string. Die Tatsache, dass dies nicht das Standardbibliotheksmodul ist, stringkommt nicht zum Vorschein.

Sie können die Datei als Teil des pkgPakets ausführen

python -m pkg.script

In diesem Fall ist die pkg Verzeichnis nicht zum Pfad hinzugefügt. Das aktuelle Verzeichnis wird jedoch zum Pfad hinzugefügt.

Sie können auch ein Boilerplate hinzufügen pkg/script.py, damit Python es als Teil des pkgPakets behandelt, selbst wenn es als Datei ausgeführt wird:

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

Dies hat jedoch keine Auswirkungen sys.path. Sie benötigen eine zusätzliche Behandlung, um das pkgVerzeichnis aus dem Pfad zu entfernen. Wenn sich pkgdas übergeordnete Verzeichnis nicht im Pfad befindet, müssen Sie dies auch im Pfad festhalten.

user2357112 unterstützt Monica
quelle
2
OK, ich meine, das verstehe ich. Das ist genau das Verhalten, das mein Beitrag dokumentiert. Angesichts dessen jedoch zwei Fragen: (1.) Wenn "das nicht genau stimmt", warum sagen die Dokumente dies kategorisch? und (2.) Wie also Sie tun , import stringwenn Sie es versehentlich Schatten, zumindest ohne durch Drall sys.modules. Ist das nicht das, was from __future__ import absolute_importverhindern soll? Was tut es? (PS, ich bin nicht der Downvoter.)
Zwei-Bit-Alchemist
14
Ja, das war ich (Downvote für "nicht nützlich", nicht für "falsch"). Aus dem unteren Abschnitt geht hervor, dass das OP versteht, wie es sys.pathfunktioniert, und die eigentliche Frage wurde überhaupt nicht beantwortet. Das heißt, was macht from __future__ import absolute_importeigentlich?
wim
5
@ Two-BitAlchemist: 1) Das Changelog ist lose formuliert und nicht normativ. 2) Sie hören auf, es zu beschatten. Selbst wenn Sie durchwühlen, sys.moduleserhalten Sie das Standardbibliotheksmodul nicht, stringwenn Sie es mit Ihrem eigenen Modul der obersten Ebene beschattet haben. from __future__ import absolute_importsoll nicht verhindern, dass Module der obersten Ebene Module der obersten Ebene beschatten; Es soll verhindern, dass paketinterne Module Module der obersten Ebene beschatten. Wenn Sie die Datei als Teil des pkgPakets ausführen , werden die internen Dateien des Pakets nicht mehr als oberste Ebene angezeigt.
user2357112 unterstützt Monica
@ Two-BitAlchemist: Antwort überarbeitet. Ist diese Version hilfreicher?
user2357112 unterstützt Monica
1
@storen: Angenommen, es pkghandelt sich um ein Paket im Importsuchpfad python -m pkg.main. -mbenötigt einen Modulnamen, keinen Dateipfad.
user2357112 unterstützt Monica
44

Der Unterschied zwischen absoluten und relativen Importen kommt nur zum Tragen, wenn Sie ein Modul aus einem Paket importieren und dieses Modul ein anderes Submodul aus diesem Paket importiert. Sieh den Unterschied:

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

Bestimmtes:

$ 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

Beachten Sie, dass python2 pkg/main2.pydas Verhalten anders ist als das Starten python2und Importieren pkg.main2(was der Verwendung von entspricht-m Schalters entspricht).

Wenn Sie jemals ein Submodul eines Pakets ausführen möchten, verwenden Sie immer den -mSchalter, der den Interpreter daran hindert, das zu verkettensys.path Liste und die Semantik des Submoduls korrekt behandelt.

Außerdem bevorzuge ich die Verwendung expliziter relativer Importe für Paket-Submodule, da diese im Fehlerfall mehr Semantik und bessere Fehlermeldungen bieten.

Bakuriu
quelle
Im Grunde funktioniert es also nur in einem engen Fall, in dem Sie das Problem "aktuelles Verzeichnis" vermieden haben? Dies scheint eine viel schwächere Implementierung zu sein als in PEP 328 und dem 2.5-Changelog beschrieben. Glauben Sie, dass die Dokumentation ungenau ist?
Zwei-Bit-Alchemist
@ Two-BitAlchemist Eigentlich machst du den "engen Fall". Sie starten nur eine einzige auszuführende Python-Datei, dies kann jedoch Hunderte von Importen auslösen. Submodule eines Pakets sollten einfach nicht ausgeführt werden, das ist alles.
Bakuriu
Warum python2 pkg/main2.pyhat ein anderes Verhalten als das Starten von python2 und das Importieren von pkg.main2?
Storen
1
@storen Das liegt daran, dass sich das Verhalten bei relativen Importen ändert. Wenn Sie starten pkg/main2.pyPython (Version 2) ist nicht zu behandeln pkgals Paket. Berücksichtigen python2 -m pkg.main2Sie beim Verwenden oder Importieren, dass es sich um pkgein Paket handelt.
Bakuriu