1. 程式人生 > >Python 的模組搜尋路徑!

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 切換一下目錄後代碼就可能不工作了。

小結:

  1. sys.path 能列出模組搜尋順序,Python REPL 中第一個路徑為空字串,代表 Shell 啟動時的目錄,執行指令碼檔案時第一個路徑是指令碼檔案所在的目錄
  2. PYTHONPATH 環境變數可以使用絕對和相對路徑。相對路徑相對於 Shell 啟動時目錄或程式指令碼檔案所在目錄
  3. PYTHONPATH 中的所有路徑會以 sys.path[1:1] = <paths in PYTHONPATH> 的方式新增到 sys.path 搜尋路徑列表中
  4. 動態的改動 sys.path 應該儘量少用吧
  5.