Cosa fa effettivamente __future__ import absolute_import?


164

Ho risposto a una domanda relativa alle importazioni assolute in Python, che pensavo di aver capito basandomi sulla lettura del log delle modifiche di Python 2.5 e sull'accompagnamento di PEP . Tuttavia, dopo aver installato Python 2.5 e aver tentato di creare un esempio di utilizzo corretto from __future__ import absolute_import, mi rendo conto che le cose non sono così chiare.

Direttamente dal log delle modifiche sopra citato, questa affermazione ha riassunto accuratamente la mia comprensione del cambiamento di importazione assoluto:

Supponiamo che tu abbia una directory dei pacchetti come questa:

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

Questo definisce un pacchetto chiamato pkgcontenente i pkg.maine pkg.stringsottomoduli.

Considera il codice nel modulo main.py. Cosa succede se esegue l'istruzione import string? In Python 2.4 e precedenti, cercherà prima nella directory del pacchetto di eseguire un'importazione relativa, trova pkg / string.py, importa il contenuto di quel file come pkg.stringmodulo e quel modulo è associato al nome "string"nello pkg.mainspazio dei nomi del modulo.

Quindi ho creato questa esatta struttura di directory:

$ ls -R
.:
pkg/

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

__init__.pye string.pysono vuoti. main.pycontiene il seguente codice:

import string
print string.ascii_uppercase

Come previsto, l'esecuzione di questo con Python 2.5 non riesce 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'

Tuttavia, più avanti nel log delle modifiche 2.5, troviamo questo (enfasi aggiunta):

In Python 2.5, puoi cambiare importil comportamento in importazioni assolute usando una from __future__ import absolute_importdirettiva. Questo comportamento di importazione assoluta diventerà il valore predefinito in una versione futura (probabilmente Python 2.7). Una volta che le importazioni assolute sono predefinite, import stringtroverà sempre la versione della libreria standard.

Ho così creato pkg/main2.py, identico main.pyma con la futura direttiva sulle importazioni aggiuntiva. Ora sembra così:

from __future__ import absolute_import
import string
print string.ascii_uppercase

L'esecuzione con Python 2.5, tuttavia ... non riesce 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'

Ciò contraddice in modo piuttosto netto l'affermazione che import stringtroverà sempre la versione std-lib con le importazioni assolute abilitate. Inoltre, nonostante l'avvertimento che le importazioni assolute sono programmate per diventare il comportamento "nuovo predefinito", ho riscontrato questo stesso problema utilizzando sia Python 2.7, con o senza la __future__direttiva:

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

così come Python 3.5, con o senza (supponendo che l' printistruzione sia cambiata in entrambi i file):

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

Ho testato altre varianti di questo. Invece di string.py, ho creato un modulo vuoto - una directory di nome stringche contiene solo un vuoto __init__.py- e, invece di emettere importazioni da main.py, ho cd'd per pkged eseguire le importazioni direttamente dal REPL. Nessuna di queste variazioni (né una combinazione di esse) ha modificato i risultati sopra. Non posso conciliare questo con ciò che ho letto sulla __future__direttiva e sulle importazioni assolute.

Mi sembra che ciò sia facilmente spiegabile come segue (questo proviene dai documenti di Python 2 ma questa affermazione rimane invariata negli stessi documenti di Python 3):

sys.path

(...)

Come inizializzato all'avvio del programma, il primo elemento di questo elenco path[0], è la directory contenente lo script utilizzato per richiamare l'interprete Python. Se la directory di script non è disponibile (ad es. Se l'interprete viene richiamato in modo interattivo o se lo script viene letto dall'input standard), path[0]è la stringa vuota che indirizza Python a cercare prima i moduli nella directory corrente.

Quindi cosa mi sto perdendo? Perché la __future__dichiarazione sembra non fare ciò che dice, e qual è la soluzione di questa contraddizione tra queste due sezioni della documentazione, nonché tra il comportamento descritto e quello reale?


Risposte:


104

Il log delle modifiche è formulato in modo sciatto. from __future__ import absolute_importnon importa se qualcosa fa parte della libreria standard e import stringnon ti fornirà sempre il modulo libreria standard con importazioni assolute.

from __future__ import absolute_importsignifica che se si import string, Python cercherà sempre un stringmodulo di livello superiore , piuttosto che current_package.string. Tuttavia, non influisce sulla logica utilizzata da Python per decidere quale file è il stringmodulo. Quando lo fai

python pkg/script.py

pkg/script.pynon sembra parte di un pacchetto per Python. Seguendo le normali procedure, la pkgdirectory viene aggiunta al percorso e tutti i .pyfile nella pkgdirectory sembrano moduli di livello superiore. import stringtrova pkg/string.pynon perché sta eseguendo un'importazione relativa, ma perché pkg/string.pysembra essere il modulo di livello superiore string. Il fatto che questo non sia il stringmodulo standard della libreria non emerge.

Per eseguire il file come parte del pkgpacchetto, è possibile farlo

python -m pkg.script

In questo caso, la pkgdirectory non verrà aggiunta al percorso. Tuttavia, la directory corrente verrà aggiunta al percorso.

Puoi anche aggiungere un po 'di boilerplate per pkg/script.pyfare in modo che Python lo tratti come parte del pkgpacchetto anche quando eseguito come file:

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

Tuttavia, questo non influirà sys.path. Avrai bisogno di un po 'di gestione aggiuntiva per rimuovere la pkgdirectory dal percorso e se pkgla directory padre non è sul percorso, dovrai incollarla anche sul percorso.


2
OK, intendo, capisco. Questo è esattamente il comportamento che il mio post sta documentando. A fronte di ciò, però, due domande: (1.) Se "non è esattamente vero", perché i documenti dicono categoricamente che lo è? e, (2.) Come, quindi, import stringse l'ombra accidentalmente, almeno senza sfogliare sys.modules. Non è questo ciò che from __future__ import absolute_importè destinato a prevenire? Che cosa fa? (PS, non sono il downvoter.)
Two-Bit Alchemist,

14
Sì, ero io (voto negativo per "non utile", non per "sbagliato"). Dalla sezione in basso è chiaro che il PO comprende come sys.pathfunziona e la vera domanda non è stata affrontata affatto. Cioè, cosa fa from __future__ import absolute_importeffettivamente?
mercoledì

5
@ Two-BitAlchemist: 1) Il log delle modifiche è liberamente definito e non normativo. 2) Smetti di oscurarlo. Anche sfogliare sys.modulesnon ti farà ottenere il stringmodulo libreria standard se lo hai ombreggiato con il tuo modulo di livello superiore. from __future__ import absolute_importnon intende impedire ai moduli di livello superiore di oscurare i moduli di livello superiore; dovrebbe impedire ai moduli interni al pacchetto di oscurare i moduli di livello superiore. Se si esegue il file come parte del pkgpacchetto, i file interni del pacchetto non verranno più visualizzati.
user2357112 supporta Monica il

@ Two-BitAlchemist: risposta rivista. Questa versione è più utile?
user2357112 supporta Monica il

1
@storen: Supponendo che pkgsia un pacchetto sul percorso di ricerca dell'importazione, dovrebbe esserlo python -m pkg.main. -mrichiede un nome del modulo, non un percorso del file.
user2357112 supporta Monica l'

44

La differenza tra importazioni assolute e relative entra in gioco solo quando si importa un modulo da un pacchetto e quel modulo importa un altro sottomodulo da quel pacchetto. Vedi la differenza:

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

In particolare:

$ 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

Si noti che python2 pkg/main2.pyha un comportamento diverso rispetto all'avvio python2e quindi all'importazione pkg.main2(che equivale a utilizzare l' -minterruttore).

Se si desidera eseguire un sottomodulo di un pacchetto, utilizzare sempre l' -mopzione che impedisce all'interprete di concatenare l' sys.pathelenco e gestisce correttamente la semantica del sottomodulo.

Inoltre, preferisco di gran lunga utilizzare le importazioni relative esplicite per i sottomoduli del pacchetto poiché forniscono più semantica e migliori messaggi di errore in caso di errore.


Quindi essenzialmente funziona solo per un caso ristretto in cui hai evitato il problema della "directory corrente"? Sembra un'implementazione molto più debole di quella descritta da PEP 328 e dal log delle modifiche 2.5. Credi che la documentazione non sia accurata?
Two-Bit Alchemist,

@ Two-BitAlchemist In realtà quello che stai facendo è il "caso ristretto". Si avvia solo un singolo file Python da eseguire, ma ciò può innescare centinaia di importazioni. I sottomoduli di un pacchetto semplicemente non dovrebbero essere eseguiti, tutto qui.
Bakuriu,

perché python2 pkg/main2.pyha un comportamento diverso dall'avvio di python2 e dall'importazione di pkg.main2?
immagazzinata l'

1
@storen Questo perché il comportamento con le relative importazioni cambia. Quando si avvia pkg/main2.pypython (versione 2) non viene considerato pkgcome un pacchetto. Durante l'utilizzo python2 -m pkg.main2o l'importazione fare tener conto che pkgè un pacchetto.
Bakuriu,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.