現象
做項目的時候,一直使用 os.path.dirname(os.path.abspath(__file__)) 來獲取當前目錄。然而,最近卻遇到了一個路徑相關的問題。直接運行 py 文件是正常的,但是打包成 exe 之后,卻顯示因為路徑問題導致程序報錯無法繼續執行。
比如我有一段獲取當前目錄的腳本代碼:
test.py
import os
import timedef get_current_dir():return os.path.dirname(os.path.abspath(__file__))if __name__ == '__main__':current_dir = get_current_dir()print(current_dir) # E:\lky_project\tmp_project\test_projecttime.sleep(10)
正常用 Python 解釋器執行的話輸出?E:\lky_project\tmp_project\test_project,看起來沒什么問題。
好,接下來,我用下面的命令對 test.py 打包,打包后將生成的 test.exe 移動到和 test.py 相同的目錄下(確保執行目錄一致)。
E:\lky_project\tmp_project\test_project> pyinstaller -F ./test.py
然后雙擊運行 test.exe,結果輸出的目錄卻是下面這種(看起來是個臨時目錄)。
顯然,這兩個輸出不是同一個目錄,而我中間只是做了打包的操作,并沒有對代碼進行任何額外修改,但為什么輸出的目錄卻不一樣呢?
原因
我搜了一下 DeepSeek,其中的分析我還是比較認同的,所以就不多說了。分析的原因如下:
- __file__ 變量通常是指當前執行腳本的路徑。當直接運行腳本時,__file__ 會返回該腳本的文件名,然后通過 os.path.abspath 獲取絕對路徑,再取目錄名,得到的就是腳本所在的目錄路徑。這應該是正確的。
- 但是當打包成 exe 后,比如使用 PyInstaller,情況可能會不同。PyInstaller 打包后的 exe 文件會將腳本解壓到一個臨時目錄中運行,這時候 __file__ 可能指向的是這個臨時目錄中的路徑,而不是原來的腳本位置。這就會導致 current_dir 在打包后得到的是臨時目錄的路徑,而不是用戶期望的 exe 所在的目錄。
解決
既然原因找到了,總得找個解決方案。
sys.frozen
其中一個解決方法,利用程序打包前和打包后 sys.frozen 環境變量的不同來進行區分,不同情況使用不同的目錄獲取方式。
import sys
import os
import timedef get_current_dir():if getattr(sys, 'frozen', False):# 打包后的情況,使用 sys.executable 的目錄print("frozen")return os.path.dirname(sys.executable)else:# 打包前,正常腳本執行print("not frozen")return os.path.dirname(os.path.abspath(__file__))current_dir = get_current_dir()
print(current_dir)time.sleep(10)
os.getcwd()
再有就是使用 os.getcwd() 來獲取當前目錄。
import os
import timedef get_current_dir():return os.path.abspath(os.getcwd())current_dir = get_current_dir()
print(current_dir)time.sleep(10)
pathlib
或者使用 pathlib 的?absolute() 或 resolve() 方法也可以。
import time
import pathlibdef get_current_dir():# return pathlib.Path("./").resolve()return pathlib.Path("./").absolute()current_dir = get_current_dir()
print(current_dir)time.sleep(10)