Bagaimana cara menentukan direktori skrip saat ini?

261

Saya ingin melihat apa cara terbaik untuk menentukan direktori skrip saat ini di python?

Saya menemukan bahwa, karena banyak cara memanggil kode python, sulit untuk menemukan solusi yang baik.

Berikut ini beberapa masalah:

  • __file__tidak ditentukan jika skrip dieksekusi dengan exec,execfile
  • __module__ didefinisikan hanya dalam modul

Gunakan kasing:

  • ./myfile.py
  • python myfile.py
  • ./somedir/myfile.py
  • python somedir/myfile.py
  • execfile('myfile.py') (dari skrip lain, yang dapat ditemukan di direktori lain dan yang dapat memiliki direktori saat ini.

Saya tahu bahwa tidak ada solusi yang sempurna, tetapi saya sedang mencari pendekatan terbaik yang memecahkan sebagian besar kasus.

Pendekatan yang paling banyak digunakan adalah os.path.dirname(os.path.abspath(__file__))tetapi ini benar-benar tidak berhasil jika Anda menjalankan skrip dari yang lain dengan exec().

Peringatan

Solusi apa pun yang menggunakan direktori saat ini akan gagal, ini bisa berbeda berdasarkan pada cara skrip dipanggil atau dapat diubah di dalam skrip yang sedang berjalan.

bogdan
sumber
1
Bisakah Anda lebih spesifik dari mana Anda perlu tahu dari mana file itu berasal? - dalam kode yang mengimpor file (termasuk host-aware) atau dalam file yang diimpor? (budak yang sadar diri)
synthesizerpatel
3
Lihat pathlibsolusi Ron Kalian jika Anda menggunakan python 3.4 atau lebih tinggi: stackoverflow.com/a/48931294/1011724
Dan
Jadi solusinya BUKAN menggunakan direktori saat ini dalam kode, tetapi menggunakan beberapa file konfigurasi?
ZhaoGang
Penemuan yang menarik, saya baru saja membuat: Ketika melakukan python myfile.pydari shell, ia bekerja, tetapi keduanya :!python %dan :!python myfile.pydari dalam vim gagal dengan Sistem tidak dapat menemukan jalur yang ditentukan. Ini sangat menjengkelkan. Adakah yang bisa berkomentar tentang alasan di balik ini dan solusi potensial?
inVader

Jawaban:

231
os.path.dirname(os.path.abspath(__file__))

memang yang terbaik yang akan Anda dapatkan.

Tidak biasa mengeksekusi skrip dengan exec/ execfile; biasanya Anda harus menggunakan infrastruktur modul untuk memuat skrip. Jika Anda harus menggunakan metode ini, saya sarankan pengaturan __file__di globalsAnda meneruskan ke skrip sehingga dapat membaca nama file itu.

Tidak ada cara lain untuk mendapatkan nama file dalam kode sebelumnya: seperti yang Anda perhatikan, CWD mungkin berada di tempat yang sama sekali berbeda.

bobince
sumber
2
Jangan pernah bilang tidak akan pernah? Menurut ini: stackoverflow.com/a/18489147 menjawab solusi lintas platform abspath (getsourcefile (lambda: 0))? Atau ada hal lain yang saya lewatkan?
Jeff Ellen
131

Jika Anda benar-benar ingin membahas kasus dimana sebuah skrip dipanggil execfile(...), Anda dapat menggunakan inspectmodul untuk menyimpulkan nama file (termasuk path). Sejauh yang saya ketahui, ini akan berfungsi untuk semua kasus yang Anda daftarkan:

filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))
Sven Marnach
sumber
4
Saya pikir ini memang metode yang paling kuat, tapi saya mempertanyakan kebutuhan OP lain untuk ini. Saya sering melihat pengembang melakukan ini ketika mereka menggunakan file data di lokasi relatif terhadap modul pelaksana, tetapi file data IMO harus diletakkan di lokasi yang diketahui.
Ryan Ginstrom
14
@Ryan LOL, jika akan lebih bagus jika Anda dapat mendefinisikan "lokasi yang diketahui" yang multi platform dan yang juga dilengkapi dengan modul. Saya siap bertaruh bahwa satu-satunya lokasi aman adalah lokasi skrip. Catatan, ini bukan berarti skrip harus menulis ke lokasi ini, tetapi untuk membaca data itu aman.
Sorin
1
Namun, solusinya tidak baik, coba saja panggil chdir()sebelum fungsi, itu akan mengubah hasilnya. Juga memanggil skrip python dari direktori lain akan mengubah hasilnya, jadi itu bukan solusi yang baik.
sorin
2
os.path.expanduser("~")adalah cara lintas platform untuk mendapatkan direktori pengguna. Sayangnya, itu bukan praktik terbaik Windows untuk menempelkan data aplikasi.
Ryan Ginstrom
6
@sorin: Saya sudah mencoba chdir()sebelum menjalankan skrip; ini menghasilkan hasil yang benar. Saya sudah mencoba memanggil skrip dari direktori lain dan juga berfungsi. Hasilnya sama dengan inspect.getabsfile()solusi berbasis .
jfs
43
#!/usr/bin/env python
import inspect
import os
import sys

def get_script_dir(follow_symlinks=True):
    if getattr(sys, 'frozen', False): # py2exe, PyInstaller, cx_Freeze
        path = os.path.abspath(sys.executable)
    else:
        path = inspect.getabsfile(get_script_dir)
    if follow_symlinks:
        path = os.path.realpath(path)
    return os.path.dirname(path)

print(get_script_dir())

Ini bekerja pada CPython, Jython, Pypy. Ini berfungsi jika skrip dieksekusi menggunakan execfile()( sys.argv[0]dan __file__solusi berbasis akan gagal di sini). Ini berfungsi jika skrip berada di dalam file zip yang dapat dieksekusi (/ telur) . Ini berfungsi jika skrip "diimpor" ( PYTHONPATH=/path/to/library.zip python -mscript_to_run) dari file zip; mengembalikan jalur arsip dalam kasus ini. Ini berfungsi jika skrip dikompilasi menjadi executable mandiri ( sys.frozen). Ini berfungsi untuk symlink ( realpathmenghilangkan tautan simbolik). Ia bekerja dalam interpreter interaktif; mengembalikan direktori kerja saat ini dalam kasus ini.

jfs
sumber
Bekerja dengan sangat baik dengan PyInstaller.
Gaborous
1
Apakah ada alasan mengapa getabsfile(..)tidak disebutkan dalam dokumentasi untukinspect ? Itu muncul di sumber yang ditautkan dari halaman itu.
Evgeni Sergeev
@ EvgeniSergeev itu mungkin bug. Ini adalah pembungkus sederhana getsourcefile(), getfile()yang didokumentasikan.
jfs
24

Dalam Python 3.4+ Anda dapat menggunakan pathlibmodul yang lebih sederhana :

from inspect import currentframe, getframeinfo
from pathlib import Path

filename = getframeinfo(currentframe()).filename
parent = Path(filename).resolve().parent
Eugene Yarmash
sumber
2
Kesederhanaan luar biasa!
Cometsong
3
Anda mungkin dapat menggunakan Path(__file__)(tidak perlu inspectmodul).
Peque
@Peque melakukan hal itu menghasilkan jalur termasuk nama file saat ini, bukan direktori induk. Jika saya mencoba untuk mendapatkan direktori skrip saat ini untuk menunjuk ke sebuah file di direktori yang sama, misalnya mengharapkan untuk memuat file konfigurasi di direktori yang sama dengan skrip, Path(__file__)berikan /path/to/script/currentscript.pyketika OP ingin mendapatkan/path/to/script/
Davos
8
Oh, saya salah paham, Anda bermaksud menghindari modul inspeksi dan hanya menggunakan sesuatu seperti parent = Path(__file__).resolve().parent itu. Jauh lebih baik.
Davos
3
@Dut A. Anda harus menggunakan .joinpath()(atau /operator) untuk ini, bukan +.
Eugene Yarmash
13

The os.path...pendekatan adalah 'hal yang dilakukan' dengan Python 2.

Di Python 3, Anda dapat menemukan direktori skrip sebagai berikut:

from pathlib import Path
cwd = Path(__file__).parents[0]
Ron Kalian
sumber
11
Atau adil Path(__file__).parent. Tapi cwditu keliru, itu bukan direktori kerja saat ini , tetapi direktori file . Mereka mungkin sama, tetapi biasanya tidak demikian.
Nuno André
5

Cukup gunakan os.path.dirname(os.path.abspath(__file__))dan periksa dengan sangat hati-hati apakah ada kebutuhan nyata untuk kasus di mana execdigunakan. Ini bisa menjadi tanda desain bermasalah jika Anda tidak dapat menggunakan skrip Anda sebagai modul.

Ingatlah Zen dari Python # 8 , dan jika Anda yakin ada argumen yang bagus untuk kasus penggunaan di mana harus digunakan exec, maka silakan beri tahu kami beberapa detail lebih lanjut tentang latar belakang masalah.

wim
sumber
2
Jika Anda tidak menjalankan dengan exec () Anda akan kehilangan konteks debugger. Exec () juga seharusnya jauh lebih cepat daripada memulai proses baru.
sorin
@sorin Ini bukan soal eksekutif vs memulai proses baru, jadi itu argumen strawman. Ini pertanyaan tentang exec vs menggunakan impor atau fungsi panggilan.
wim
4

Akan

import os
cwd = os.getcwd()

lakukan apa yang kamu inginkan? Saya tidak yakin apa yang Anda maksud dengan "direktori skrip saat ini". Apa yang diharapkan dari output untuk kasus penggunaan yang Anda berikan?

Will McCutchen
sumber
3
Itu tidak akan membantu. Saya percaya @bogdan sedang mencari direktori untuk skrip yang ada di bagian atas tumpukan panggilan. yaitu dalam semua kasusnya, ia harus mencetak direktori tempat duduk 'myfile.py'. Namun metode Anda hanya akan mencetak direktori file yang memanggil exec('myfile.py'), sama seperti __file__dan sys.argv[0].
Zhang18
Ya, itu masuk akal. Saya hanya ingin memastikan @bogdan tidak menghadap ke sesuatu yang sederhana, dan saya tidak tahu persis apa yang mereka inginkan.
Will McCutchen
3

Pertama .. beberapa kasus penggunaan hilang di sini jika kita berbicara tentang cara menyuntikkan kode anonim ..

code.compile_command()
code.interact()
imp.load_compiled()
imp.load_dynamic()
imp.load_module()
__builtin__.compile()
loading C compiled shared objects? example: _socket?)

Tetapi, pertanyaan sebenarnya adalah, apa tujuan Anda - apakah Anda mencoba untuk menegakkan semacam keamanan? Atau apakah Anda hanya tertarik pada apa yang dimuat.

Jika Anda tertarik pada keamanan , nama file yang diimpor melalui exec / execfile tidak penting - Anda harus menggunakan rexec , yang menawarkan hal berikut:

Modul ini berisi kelas RExec, yang mendukung r_eval (), r_execfile (), r_exec (), dan metode r_import (), yang merupakan versi terbatas dari fungsi standar Python eval (), execfile () dan pernyataan exec dan impor. Kode yang dijalankan di lingkungan terbatas ini hanya akan memiliki akses ke modul dan fungsi yang dianggap aman; Anda dapat subkelas RExec menambah atau menghapus kemampuan yang diinginkan.

Namun, jika ini lebih merupakan pengejaran akademis .. berikut adalah beberapa pendekatan konyol yang mungkin bisa Anda gali lebih dalam ..

Contoh skrip:

./deep.py

print ' >> level 1'
execfile('deeper.py')
print ' << level 1'

./deeper.py

print '\t >> level 2'
exec("import sys; sys.path.append('/tmp'); import deepest")
print '\t << level 2'

/tmp/deepest.py

print '\t\t >> level 3'
print '\t\t\t I can see the earths core.'
print '\t\t << level 3'

./codespy.py

import sys, os

def overseer(frame, event, arg):
    print "loaded(%s)" % os.path.abspath(frame.f_code.co_filename)

sys.settrace(overseer)
execfile("deep.py")
sys.exit(0)

Keluaran

loaded(/Users/synthesizerpatel/deep.py)
>> level 1
loaded(/Users/synthesizerpatel/deeper.py)
    >> level 2
loaded(/Users/synthesizerpatel/<string>)
loaded(/tmp/deepest.py)
        >> level 3
            I can see the earths core.
        << level 3
    << level 2
<< level 1

Tentu saja, ini adalah cara yang intensif sumber daya untuk melakukannya, Anda akan melacak semua kode Anda .. Tidak terlalu efisien. Tapi, saya pikir ini pendekatan baru karena terus bekerja bahkan ketika Anda masuk lebih dalam ke sarang. Anda tidak dapat mengganti 'eval'. Meskipun Anda bisa menimpa execfile ().

Catatan, pendekatan ini hanya mencakup exec / execfile, bukan 'impor'. Untuk pemuatan kait 'modul' tingkat tinggi, Anda mungkin dapat menggunakan sys.path_hooks (Write-up courtesy of PyMOTW).

Itulah semua yang ada di kepala saya.

synthesizerpatel
sumber
2

Ini adalah solusi parsial, masih lebih baik dari semua yang dipublikasikan sejauh ini.

import sys, os, os.path, inspect

#os.chdir("..")

if '__file__' not in locals():
    __file__ = inspect.getframeinfo(inspect.currentframe())[0]

print os.path.dirname(os.path.abspath(__file__))

Sekarang ini berfungsi semua panggilan tetapi jika seseorang menggunakan chdir()untuk mengubah direktori saat ini, ini juga akan gagal.

Catatan:

  • sys.argv[0]tidak akan berfungsi, akan kembali -cjika Anda menjalankan skrip denganpython -c "execfile('path-tester.py')"
  • Saya menerbitkan tes lengkap di https://gist.github.com/1385555 dan Anda dipersilakan untuk memperbaikinya.
Sorin
sumber
1

Ini harus bekerja dalam banyak kasus:

import os,sys
dirname=os.path.dirname(os.path.realpath(sys.argv[0]))
Jahid
sumber
5
Solusi ini menggunakan direktori saat ini dan secara eksplisit dinyatakan dalam pertanyaan bahwa solusi tersebut akan gagal.
menjulang tinggi
1

Semoga ini membantu: - Jika Anda menjalankan skrip / modul dari mana saja Anda akan dapat mengakses __file__variabel yang merupakan variabel modul yang mewakili lokasi skrip.

Di sisi lain, jika Anda menggunakan juru bahasa Anda tidak memiliki akses ke variabel itu, di mana Anda akan mendapatkan nama NameErrordan os.getcwd()akan memberi Anda direktori yang salah jika Anda menjalankan file dari tempat lain.

Solusi ini akan memberi Anda apa yang Anda cari dalam semua kasus:

from inspect import getsourcefile
from os.path import abspath
abspath(getsourcefile(lambda:0))

Saya belum benar-benar mengujinya tetapi itu menyelesaikan masalah saya.

Menandai
sumber
Ini akan memberikan file, bukan direktori
Shital Shah