Apakah atribut modul __file__ mutlak atau relatif?

107

Saya kesulitan memahaminya __file__. Dari apa yang saya pahami, __file__mengembalikan jalur absolut dari mana modul dimuat.

Saya mengalami masalah saat membuat ini: Saya memiliki abc.pypernyataan dengan satu print __file__, berjalan dari /d/projects/ python abc.pypengembalian abc.py. lari dari /d/pengembalian projects/abc.py. Ada alasan kenapa?

goh
sumber
10
Inilah yang dikatakan Guido tentang ini: mail.python.org/pipermail/python-dev/2010-February/097461.html
kindall
Relevan: stackoverflow.com/q/9271464/1959808
Ioannis Filippidis

Jawaban:

98

Dari dokumentasi :

__file__adalah nama path dari file tempat modul dimuat, jika itu diambil dari sebuah file. The __file__atribut tidak hadir untuk modul C yang statis terhubung ke penafsir; untuk modul ekstensi yang dimuat secara dinamis dari pustaka bersama, itu adalah nama jalur dari file pustaka bersama.

Dari utas milis yang ditautkan oleh @kindall dalam komentar ke pertanyaan:

Saya belum mencoba untuk merepro contoh khusus ini, tetapi alasannya adalah kami tidak ingin memanggil getpwd () pada setiap impor dan kami juga tidak ingin memiliki semacam variabel dalam proses untuk menyimpan cache direktori saat ini. (getpwd () relatif lambat dan terkadang bisa langsung gagal, dan mencoba men-cache-nya memiliki risiko kesalahan tertentu.)

Yang kami lakukan adalah kode di site.py yang berjalan di atas elemen sys.path dan mengubahnya menjadi jalur absolut. Namun kode ini berjalan sebelum '' disisipkan di depan sys.path, sehingga nilai awal sys.path adalah ''.

Untuk selebihnya, pertimbangkan sys.pathuntuk tidak menyertakan ''.

Jadi, jika Anda berada di luar bagian sys.pathyang berisi modul, Anda akan mendapatkan jalur absolut . Jika Anda berada di dalam bagian sys.pathyang berisi modul, Anda akan mendapatkan jalur relatif .

Jika Anda memuat modul di direktori saat ini, dan direktori saat ini tidak dalam sys.path, Anda akan mendapatkan path absolut.

Jika Anda memuat modul di direktori saat ini, dan direktori saat ini ada di dalamnya sys.path, Anda akan mendapatkan jalur relatif.

agf
sumber
jadi apakah itu berarti bahwa jika ada jalur dari '' ke modul, jalur relatif akan digunakan, jika tidak jalur absolut akan digunakan karena sisa sys.path adalah absolut ..
goh
4
Jika Anda memuat modul di direktori saat ini, dan direktori saat ini tidak dalam sys.path, Anda akan mendapatkan path absolut. Jika Anda memuat modul di direktori saat ini, dan direktori saat ini ada di dalamnya sys.path, Anda akan mendapatkan jalur relatif.
agf
Ingat, untuk tujuan ini, sys.pathtidak termasuk ''.
agf
mengerti, tetapi @agf, jika saya menggunakan python /foo/abc.py dari / home, saya kira bagian dari sys.path yang berisi modul adalah / home / foo dan direktori saya saat ini adalah / home /, mengapa mencetak file memberi saya jalur relatif?
goh
55

__file__mutlak sejak Python 3.4 , kecuali saat menjalankan skrip secara langsung menggunakan jalur relatif:

__file__Atribut modul (dan nilai terkait) sekarang harus selalu berisi jalur absolut secara default, dengan satu-satunya pengecualian __main__.__file__saat skrip dijalankan secara langsung menggunakan jalur relatif. (Kontribusi Brett Cannon di bpo-18416 .)

Tidak yakin apakah itu menyelesaikan symlink.

Contoh melewatkan jalur relatif:

$ python script.py
anatoly techtonik
sumber
1
Terima kasih. Ini adalah fakta yang sulit untuk dilacak!
meawoppl
4
Ini tidak benar untuk Python 3.4.0 ( Python 3.4.0 (default, Apr 11 2014, 13:05:11) [GCC 4.8.2] on linux). Dan symlink tidak diselesaikan dalam uji coba saya.
Frozen Flame
@F FrozenFlame, laporkan ke bugs.python.org jika 3.4.1 tidak memperbaikinya.
anatoly techtonik
2
Apakah os.path.realpath(__file__)cara yang benar untuk menyelesaikan tautan simbolik?
kevinarpe
3
@kevinarpe, stackoverflow.com/questions/3220755/…
anatoly techtonik
16

Contoh sederhana yang terlambat:

from os import path, getcwd, chdir

def print_my_path():
    print('cwd:     {}'.format(getcwd()))
    print('__file__:{}'.format(__file__))
    print('abspath: {}'.format(path.abspath(__file__)))

print_my_path()

chdir('..')

print_my_path()

Di bawah Python-2. *, Panggilan kedua salah menentukan path.abspath(__file__)berdasarkan direktori saat ini:

cwd:     C:\codes\py
__file__:cwd_mayhem.py
abspath: C:\codes\py\cwd_mayhem.py
cwd:     C:\codes
__file__:cwd_mayhem.py
abspath: C:\codes\cwd_mayhem.py

Seperti dicatat oleh @techtonik, dengan Python 3.4+, ini akan berfungsi dengan baik karena __file__mengembalikan jalur absolut.

SimplyKnownAsG
sumber
... kecuali untuk __main__modul, di mana __file__ mungkin merupakan jalur relatif.
0xC0000022L
5

Dengan bantuan mail Guido yang disediakan oleh @kindall, kita dapat memahami proses impor standar seperti mencoba menemukan modul di setiap anggota sys.path, dan file sebagai hasil pencarian ini (detail selengkapnya di PyMOTW Modules and Imports .). Jadi jika modul terletak di jalur absolut sys.pathhasilnya adalah absolut, tetapi jika berada di jalur relatif sys.pathhasilnya adalah relatif.

Sekarang site.pyfile startup hanya menangani pengiriman jalur absolut sys.path, kecuali inisial '', jadi jika Anda tidak mengubahnya dengan cara lain selain menyetel PYTHONPATH (yang jalurnya juga dibuat absolut, sebelum memberi awalan sys.path), Anda akan selalu mendapatkan absolut path, tetapi ketika modul diakses melalui direktori saat ini.

Sekarang jika Anda menipu sys.path dengan cara yang lucu, Anda bisa mendapatkan apa saja.

Sebagai contoh jika Anda memiliki modul sampel foo.pydi /tmp/dengan kode:

import sys
print(sys.path)
print (__file__)

Jika Anda masuk / tmp Anda mendapatkan:

>>> import foo
['', '/tmp', '/usr/lib/python3.3', ...]
./foo.py

Ketika masuk /home/user, jika Anda menambahkan /tmpAnda, PYTHONPATHAnda mendapatkan:

>>> import foo
['', '/tmp', '/usr/lib/python3.3', ...]
/tmp/foo.py

Bahkan jika Anda menambahkan ../../tmp, itu akan menjadi normal dan hasilnya sama.

Tetapi jika alih-alih menggunakan PYTHONPATHAnda menggunakan secara langsung beberapa jalur lucu Anda mendapatkan hasil yang lucu sebagai penyebabnya.

>>> import sys
>>> sys.path.append('../../tmp')
>>> import foo
['', '/usr/lib/python3.3', .... , '../../tmp']
../../tmp/foo.py

Guido menjelaskan di utas yang dikutip di atas, mengapa python tidak mencoba mengubah semua entri di jalur absolut:

kami tidak ingin memanggil getpwd () pada setiap impor .... getpwd () relatif lambat dan terkadang dapat gagal secara langsung,

Jadi jalan Anda digunakan apa adanya .

marcz
sumber