Python 的模組搜尋路徑!
一種語言要使用到外部庫(模組) 時必然會涉及到從哪裡以及按何順序載入依賴,就像 LD_LIBRARY_PATH, CLASSPATH 那樣,Python 也有其預設的模組搜尋順序, 依序找到想要的模組即停止。Python 中 sys.path 返回的列表包含了模組搜尋的順序,我們可以程式中修改該列表,或用 PYTHONPATH 環境變數前插路徑,甚至是用 .pth 檔案來附加路徑。
簡單的,可以執行命令 python3 -c "import sys; print(str(sys.path).replace(',', ' '))" 來檢視 python3 互動 shell 下的模組搜尋路徑,類似結果如下:
['' '/usr/lib/python36.zip' '/usr/lib/python3.6' '/usr/lib/python3.6/lib-dynload' '/home/yanbin/.local/lib/python3.6/site-packages' '/usr/local/lib/python3.6/dist-packages' '/usr/lib/python3/dist-packages' '/usr/lib/python3.6/dist-packages']
注意,第一個元素是個空字串,代表進入 python3 shell 時的當前目錄。
如果在通過一個 py 指令碼檔案來列印 sys.path 的話顯示稍微有所差異。比如在目錄 /home/yanbin/Developers/ 下建立 test.py 檔案,內容為
import sys for line in sys.path: print(f"'{line}'")
而我們退到 /home/yanbin 為當前目錄來執行 python3 Developers/test.py , 顯示出來的搜尋路徑如下:
'/home/yanbin/Developers' '/usr/lib/python36.zip' '/usr/lib/python3.6' '/usr/lib/python3.6/lib-dynload' '/home/yanbin/.local/lib/python3.6/site-packages' '/usr/local/lib/python3.6/dist-packages' '/usr/lib/python3/dist-packages' '/usr/lib/python3.6/dist-packages'
第一行的路徑有所不同了,變成了一個絕對路徑,也就是說 test.py 可以從它所在目錄上載入模組。比如在 /home/yanbin/Developers 下有 mymath.py , 在 test.py 中能夠直接 import mymath 成功。但是把 mymath.py 放到當前目錄 /home/yanbin 下就載入不到了。除非用 PYTHONPATH 指定當前目錄 . , 後面會講到。
再次重複一下 Python shell 與執行指令碼檔案時,模組搜尋路徑第一個路徑顯示方式不一樣,Python shell 下用空字串表示進行 shell 時的當前目錄,執行指令碼時第一個路徑是指令碼檔案所在的目錄。Shell 啟動時目錄或指令碼所以在目錄總是最高優先順序的模組搜尋路徑。
接下來介紹如何動態修改 sys.path 以及用環境變數 PYTHONPATH 如何影響 sys.path 來增加新的搜尋路徑。至於用 .pth 檔案附加路徑的方式後面會另立新篇。
動態修改 sys.path
sys.path 是一個可變的列表,所以執行時可以隨便修改它的內容,比如說想要載入 /home/yanbin/test 下的 mymath 模組,但是它沒出現在 sys.path 列表中,我們可以這樣做
import sys sys.path.append('/home/yanbin/test') import mymath print(mymath.pi)
這只是一條路子,程式中不應該經常性的這麼做,因為 sys.path 是一個全域性變數,如果執行時想改就改,最終不知道模組是從哪兒載入過來的。相比,下面的 PYTHONPATH 比較實用些
PYTHONPATH 環境變數向前新增模組搜尋路徑
如果用環境變數 PYTHONPATH 設定了模組搜尋路徑,它的內容將被向前新增到 sys.path 列表中去,準確來講是插入到 sys.path 的第一個元素後面。 PYTHONPATH 中可以使用絕對路徑和相對路徑,相對路徑是相對於 Shell 啟動時的目錄或指令碼檔案所在的目錄。
舉例來看
python shell
$ export PYTHONPATH=/home/yanbin/test:extra_modules $ cd / $ python3 >>> import sys >>> for line in sys.path: ... print(f"'{line}'") ... '' '/home/yanbin/test' '/extra_modules' '/usr/lib/python36.zip' '/usr/lib/python3.6' '/usr/lib/python3.6/lib-dynload' '/home/yanbin/.local/lib/python3.6/site-packages' '/usr/local/lib/python3.6/dist-packages' '/usr/lib/python3/dist-packages' '/usr/lib/python3.6/dist-packages'
看到相對路徑 extra_modules 相對於進入 shell 前的 / 目錄,效果上相當於
sys.path[1:1] = ['/home/yanbin/test', '/extra_modules']
指令碼檔案的方式
這一次我們在 PYTHONPATH 中把當前路徑給加上
$ export PYTHONPATH=.:/home/yanbin/test:extra_modules $ cd /home/yanbin $ python3 Developers/test.py
輸出如下
'/home/yanbin/Developers' '/home/yanbin' '/home/yanbin/test' '/home/yanbin/extra_modules' '/usr/lib/python36.zip' '/usr/lib/python3.6' '/usr/lib/python3.6/lib-dynload' '/home/yanbin/.local/lib/python3.6/site-packages' '/usr/local/lib/python3.6/dist-packages' '/usr/lib/python3/dist-packages' '/usr/lib/python3.6/dist-packages'
第一行為指令碼檔案所在的目錄, /home/yanbin 為 PYTHONPATH 中的 . 。這樣我們既能夠從指令碼所在的目錄,也可以從當前工作目錄中載入模組了,驚喜就是 cd 切換一下目錄後代碼就可能不工作了。
小結:
- sys.path 能列出模組搜尋順序,Python REPL 中第一個路徑為空字串,代表 Shell 啟動時的目錄,執行指令碼檔案時第一個路徑是指令碼檔案所在的目錄
- PYTHONPATH 環境變數可以使用絕對和相對路徑。相對路徑相對於 Shell 啟動時目錄或程式指令碼檔案所在目錄
- PYTHONPATH 中的所有路徑會以 sys.path[1:1] = <paths in PYTHONPATH> 的方式新增到 sys.path 搜尋路徑列表中
- 動態的改動 sys.path 應該儘量少用吧