目錄

廣告 AD

Python 模組找不到?帶你探索 Python 搜索模組的秘密!

我們常常都在 Python 中 import 套件

但明明裝了套件,卻出現找不到套件時該怎麼辦呢?

廣告 AD

Python 中要引入 module 的時候,我們都知道要用 import,像是下面這樣:

python

import os

或是想要動態引入的話,可以透過 module 名稱用 importlib 引入:

python

import importlib
os = importlib.import_module("os")

但到底 Python 是怎麼找到我們要引入的 module?


當 Python 開始執行時,Python 的 module 搜尋清單會被建立,這個清單會被存放在 sys.path

因此我們可以透過印出 sys.path 來得知目前 Python 會從哪些路徑尋找 module

而第一個搜尋路徑代表著 Python Script 所存在的目錄

python

import sys
print(sys.path)

text

[
  '/home/ubuntu', 
  '/usr/lib/python310.zip', 
  '/usr/lib/python3.10', 
  '/usr/lib/python3.10/lib-dynload', 
  '/home/ubuntu/.local/lib/python3.10/site-packages', 
  '/usr/local/lib/python3.10/dist-packages', 
  '/usr/lib/python3/dist-packages'
]

而這個 sys.path 是可以自己做修改的,這樣就可以讓 Python 到這裡去找 module

python

import sys
sys.path.append("/path/to/module")

與使用 python3 test.py 不同,如果我們使用 -m 或是 -c 的話,第一個搜尋路徑代表著都是執行指令的路徑,而非 script 所在的目錄。

使用 -c,第一個路徑會是空字串,這是相對路徑,代表著執行 python3 -c 這個指令時所在的路徑

例如你在 /home/ubuntu/test 的路徑下執行 python3 -c "import sys; print(sys.path)",則第一個搜尋路徑代表著就是 /home/ubuntu/test

而因為有 zip import 的功能,也就是把 lib 壓縮成 zip file,所以路徑上也會有 .zip

text

[
  '', 
  '/usr/lib/python310.zip', 
  '/usr/lib/python3.10', 
  '/usr/lib/python3.10/lib-dynload', 
  '/home/ubuntu/.local/lib/python3.10/site-packages', 
  '/usr/local/lib/python3.10/dist-packages', 
  '/usr/lib/python3/dist-packages'
]

換作使用 -m 的話,如果我在 /home 目錄下,執行 python3 -m ubuntu.test 的指令,則第一個搜尋路徑就會是 /home

雖然與 -c 不同,不是空字串,但所代表的含意是相同的,都是指向執行指令時的路徑

text

[
  '/home', 
  '/usr/lib/python310.zip', 
  '/usr/lib/python3.10', 
  '/usr/lib/python3.10/lib-dynload', 
  '/home/ubuntu/.local/lib/python3.10/site-packages', 
  '/usr/local/lib/python3.10/dist-packages', 
  '/usr/lib/python3/dist-packages'
]

總結以上,如果我們在 /a/b/c 的目錄下執行下列兩種指令,兩者差別在於是搜尋路徑是包含 /a/b/c 還是 /a/b/c/d

  • python3 d/xxx.py: 包含 /a/b/c/d
  • python3 -m d.xxx: 包含 /a/b/c

有時候我們只是要測試某些正在開發的檔案,但這些檔案又沒有放在預設的搜尋清單中

這時候就可以透過 PYTHONPATH 這個環境變數來快速加入到 sys.path 中

shell

export PYTHONPATH="test"
python3 -c "import sys; print(sys.path)"

text

[
  '', 
  '/home/ubuntu/blog/yyblog/test', 
  '/usr/lib/python310.zip', 
  '/usr/lib/python3.10', 
  '/usr/lib/python3.10/lib-dynload', 
  '/home/ubuntu/.local/lib/python3.10/site-packages', 
  '/usr/local/lib/python3.10/dist-packages', 
  '/usr/lib/python3/dist-packages'
]

不過,PYTHONPATH 也會影響到 shell 中其他的 Python 程式,因此要謹慎使用。


一般來說,如果我們在虛擬環境裡面,我們只能使用虛擬環境中有安裝的 package,系統上安裝的是不能 import 的

但我們可以透過修改虛擬環境資料夾內的 pyvenv.cfg,例如虛擬環境在 /home/ubuntu/env,檔案就會是 /home/ubuntu/env/pyvenv.cfg

txt

home = /usr/bin
include-system-site-packages = false
version = 3.10.12

我們將其中的 include-system-site-packages 改成 true,就可以使用系統的 package 了

這點也可以從 sys.path 上看到,在修改前,include-system-site-packagesfalse

text

[
  '', 
  '/usr/lib/python310.zip', 
  '/usr/lib/python3.10', 
  '/usr/lib/python3.10/lib-dynload', 
  '/home/ubuntu/env/lib/python3.10/site-packages'
]

修改成 true 之後就多了系統的 package 安裝路徑了

text

[
  '', 
  '/usr/lib/python310.zip', 
  '/usr/lib/python3.10', 
  '/usr/lib/python3.10/lib-dynload', 
  '/home/ubuntu/env/lib/python3.10/site-packages', 
  '/home/ubuntu/.local/lib/python3.10/site-packages', 
  '/usr/local/lib/python3.10/dist-packages', 
  '/usr/lib/python3/dist-packages'
]

另外,如果在使用 venv 建立虛擬環境的時候,可以加上 --system-site-packages,這樣預設就可以使用系統的 package 了,例如:

shell

python3 -m venv env --system-site-packages

Debian 系統中,為了防止衝突,有做了一些調整:

  • site-packages: 使用者安裝的套件都會被放在 site-packages 中。
  • dist-packages: Debian 系統中,系統會自帶或是可以透過 apt 安裝 Python 套件,為了和使用者安裝的作區別,因此會被放置在 dist-packages 中,而 pip 安裝的也算是此類。

例如:

  • 系統自帶 or apt 安裝:/usr/lib/python3/dist-packages
  • pip 安裝在 system:/usr/local/lib/python3.10/dist-packages,多了個 local,進一步作區隔
  • pip 安裝在 user:/home/<user>/.local/lib/python3.10/site-packages
  • 虛擬環境 pip 安裝:path/to/env/lib/python3.10/site-packages


廣告 AD