Apa yang sebenarnya dari __future__ import absolute_import lakukan?

164

Saya telah menjawab pertanyaan tentang impor absolut dalam Python, yang saya pikir saya mengerti berdasarkan membaca changelog Python 2.5 dan menyertai PEP . Namun, setelah menginstal Python 2.5 dan mencoba membuat contoh penggunaan yang benar from __future__ import absolute_import, saya menyadari hal-hal yang tidak begitu jelas.

Langsung dari changelog yang ditautkan di atas, pernyataan ini secara akurat merangkum pemahaman saya tentang perubahan impor absolut:

Katakanlah Anda memiliki direktori paket seperti ini:

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

Ini mendefinisikan paket bernama pkgberisi pkg.maindan pkg.stringsubmodules.

Pertimbangkan kode dalam modul main.py. Apa yang terjadi jika mengeksekusi pernyataan import string? Dalam Python 2.4 dan sebelumnya, pertama-tama akan melihat direktori paket untuk melakukan impor relatif, menemukan pkg / string.py, mengimpor konten file itu sebagai pkg.stringmodul, dan modul itu terikat dengan nama "string"di pkg.mainnamespace modul.

Jadi saya membuat struktur direktori yang tepat ini:

$ ls -R
.:
pkg/

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

__init__.pydan string.pykosong. main.pyberisi kode berikut:

import string
print string.ascii_uppercase

Seperti yang diharapkan, menjalankan ini dengan Python 2.5 gagal dengan 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'

Namun, lebih jauh di dalam changelog 2.5, kami menemukan ini (penekanan ditambahkan):

Dalam Python 2.5, Anda bisa mengubah importperilaku menjadi impor absolut menggunakan from __future__ import absolute_importarahan. Perilaku impor absolut ini akan menjadi default di versi yang akan datang (mungkin Python 2.7). Setelah impor absolut adalah default, import stringakan selalu menemukan versi perpustakaan standar.

Dengan demikian saya dibuat pkg/main2.py, identik dengan main.pytetapi dengan arahan impor tambahan di masa depan. Sekarang terlihat seperti ini:

from __future__ import absolute_import
import string
print string.ascii_uppercase

Menjalankan ini dengan Python 2.5, namun ... gagal dengan 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'

Ini cukup bertentangan dengan pernyataan yang import stringakan selalu menemukan versi std-lib dengan impor absolut diaktifkan. Terlebih lagi, meskipun ada peringatan bahwa impor absolut dijadwalkan untuk menjadi perilaku "default baru", saya mengalami masalah yang sama menggunakan kedua Python 2.7, dengan atau tanpa __future__arahan:

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

serta Python 3.5, dengan atau tanpa (dengan asumsi printpernyataan diubah di kedua 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'

Saya sudah menguji variasi lain ini. Alih-alih string.py, saya telah membuat modul kosong - direktori bernama stringhanya berisi kosong __init__.py- dan bukannya mengeluarkan impor dari main.py, saya harus cdke pkgdan menjalankan impor langsung dari REPL. Tidak satu pun dari variasi ini (atau kombinasi keduanya) yang mengubah hasil di atas. Saya tidak bisa mendamaikan ini dengan apa yang saya baca tentang __future__petunjuk dan impor absolut.

Tampaknya bagi saya ini mudah dijelaskan oleh yang berikut (ini dari Python 2 docs tetapi pernyataan ini tetap tidak berubah dalam docs yang sama untuk Python 3):

sys.path

(...)

Seperti diinisialisasi pada startup program, item pertama dari daftar ini path[0],, adalah direktori yang berisi skrip yang digunakan untuk memanggil juru bahasa Python. Jika direktori skrip tidak tersedia (mis. Jika juru bahasa dipanggil secara interaktif atau jika skrip dibaca dari input standar), path[0]adalah string kosong, yang mengarahkan Python untuk mencari modul dalam direktori saat ini terlebih dahulu.

Jadi apa yang saya lewatkan? Mengapa __future__pernyataan itu tampaknya tidak melakukan apa yang dikatakannya, dan apa resolusi dari kontradiksi antara dua bagian dokumentasi ini, serta antara perilaku yang digambarkan dan yang sebenarnya?

Alkemis Dua-Bit
sumber

Jawaban:

104

Changelog adalah kata-kata sembarangan. from __future__ import absolute_importtidak peduli apakah sesuatu merupakan bagian dari perpustakaan standar, dan import stringtidak akan selalu memberi Anda modul perpustakaan standar dengan impor absolut aktif.

from __future__ import absolute_importberarti jika Anda import string, Python akan selalu mencari stringmodul tingkat atas , bukan current_package.string. Namun, itu tidak mempengaruhi logika yang digunakan Python untuk memutuskan file apa stringmodul. Saat kamu melakukan

python pkg/script.py

pkg/script.pytidak terlihat seperti bagian dari paket ke Python. Mengikuti prosedur normal, pkgdirektori ditambahkan ke path, dan semua .pyfile dalam pkgdirektori terlihat seperti modul tingkat atas. import stringmenemukan pkg/string.pybukan karena itu melakukan impor relatif, tetapi karena pkg/string.pytampaknya menjadi modul tingkat atas string. Fakta bahwa ini bukan stringmodul perpustakaan standar tidak muncul.

Untuk menjalankan file sebagai bagian dari pkgpaket, Anda bisa melakukannya

python -m pkg.script

Dalam hal ini, pkgdirektori tidak akan ditambahkan ke path. Namun, direktori saat ini akan ditambahkan ke jalur.

Anda juga dapat menambahkan beberapa boilerplate untuk pkg/script.pymembuat Python memperlakukannya sebagai bagian dari pkgpaket bahkan ketika dijalankan sebagai file:

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

Namun, ini tidak akan mempengaruhi sys.path. Anda akan memerlukan penanganan tambahan untuk menghapus pkgdirektori dari path, dan jika pkgdirektori induk tidak ada di path, Anda juga harus tetap pada path tersebut.

user2357112 mendukung Monica
sumber
2
OK, maksud saya, saya mengerti. Itulah perilaku yang didokumentasikan oleh pos saya. Namun, dalam menghadapi itu, ada dua pertanyaan: (1.) Jika "itu tidak sepenuhnya benar", mengapa menurut dokumen itu dikatakan? dan, (2.) Bagaimana, kalau begitu, import stringjika Anda secara tidak sengaja membayangi, setidaknya tanpa rifling sys.modules. Bukankah ini yang from __future__ import absolute_importdimaksudkan untuk dicegah? Apa fungsinya? (PS, saya bukan downvoter.)
Two-Bit Alchemist
14
Aye, itu aku (downvote untuk 'tidak berguna', bukan untuk 'salah'). Jelas dari bagian bawah OP memahami cara sys.pathkerjanya, dan pertanyaan sebenarnya belum ditanggapi sama sekali. Yaitu, Apa yang from __future__ import absolute_importsebenarnya dilakukan?
wim
5
@ Two-BitAlchemist: 1) changelog adalah kata yang longgar dan tidak normatif. 2) Anda berhenti membayangi itu. Bahkan dengan terus-menerus sys.modulestidak akan mendapatkan Anda stringmodul perpustakaan standar jika Anda membayangi modul tingkat atas Anda sendiri. from __future__ import absolute_importtidak dimaksudkan untuk menghentikan modul tingkat atas dari membayangi modul tingkat atas; itu seharusnya menghentikan modul internal paket dari membayangi modul tingkat atas. Jika Anda menjalankan file sebagai bagian dari pkgpaket, file internal paket berhenti muncul sebagai tingkat atas.
user2357112 mendukung Monica
@ Two-BitAlchemist: Jawaban direvisi. Apakah versi ini lebih bermanfaat?
user2357112 mendukung Monica
1
@storen: Dengan asumsi pkgadalah paket di jalur pencarian impor, itu seharusnya python -m pkg.main. -mmembutuhkan nama modul, bukan jalur file.
user2357112 mendukung Monica
44

Perbedaan antara impor absolut dan relatif ikut bermain hanya ketika Anda mengimpor modul dari suatu paket dan modul itu mengimpor submodule lain dari paket itu. Lihat perbedaannya:

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

Khususnya:

$ 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

Catatan yang python2 pkg/main2.pymemiliki perilaku berbeda kemudian meluncurkan python2dan kemudian mengimpor pkg.main2(yang setara dengan menggunakan -msakelar).

Jika Anda ingin menjalankan submodule suatu paket selalu gunakan -mswitch yang mencegah interpreter untuk merantai sys.pathdaftar dan dengan benar menangani semantik submodule.

Juga, saya lebih suka menggunakan impor relatif eksplisit untuk submodul paket karena mereka memberikan lebih banyak semantik dan pesan kesalahan yang lebih baik jika terjadi kegagalan.

Bakuriu
sumber
Jadi pada dasarnya itu hanya berfungsi untuk kasus sempit di mana Anda telah menghindari masalah "direktori saat ini"? Itu tampaknya menjadi implementasi yang jauh lebih lemah daripada yang dijelaskan oleh PEP 328 dan 2.5 changelog. Apakah Anda yakin dokumentasinya tidak akurat?
Alchemist Dua-Bit
@ Two-BitAlchemist Sebenarnya yang Anda lakukan adalah "case sempit". Anda hanya meluncurkan file python tunggal untuk dieksekusi, tetapi ini dapat memicu ratusan impor. Submodules dari sebuah paket tidak seharusnya dieksekusi, itu saja.
Bakuriu
mengapa python2 pkg/main2.pymemiliki perilaku yang berbeda kemudian meluncurkan python2 dan kemudian mengimpor pkg.main2?
storen
1
@storen Itu karena perilaku dengan impor relatif berubah. Ketika Anda meluncurkan pkg/main2.pypython (versi 2) tidak memperlakukan pkgsebagai paket. Sementara menggunakan python2 -m pkg.main2atau mengimpor melakukan memperhitungkan bahwa pkgadalah sebuah paket.
Bakuriu