VS Code 正確設定 PYTHONPATH 教學
文章目錄
前幾天,同事為專案的局部元件寫了一個偵錯小程式,我們姑且稱為debugger.py
。該程式中會使用到整個專案的共同設定——DeployStatus
,這些設定則放在專案下的configs
模組(資料夾)裡,需要另外 import。
因此,debugger.py
的開頭程式碼如下:
1 | import os |
而專案的結構則是(這裡只凸顯兩者的「相對層級關係」,其餘細節省略):
1 | . |
有經驗的你可能不用執行這個小程式就能預料——它找不到configs
!
不出所料,直接執行之後,會出現錯誤訊息:
1 | ModuleNotFoundError: No module named 'configs' |
為什麼?這牽涉到 Python 直譯器在 import 時,究竟「如何尋找 import 路徑」議題。
Python import 基礎
可先參考下列三篇文章,建立關於 import 的基礎世界觀:
從上述引用內容可知,debugger.py
所以會出現ModuleNotFoundError
,是因為from configs.config import DeployStatus
這段程式碼的「程式寫作意圖」,是希望從「專案根目錄」的角度出發去 import——但實際上 Python 直譯器卻沒有如預期地這麼做。
sys.path
因為,在這個例子中,沒有額外設定的情況下,專案根目錄並不在sys.path
裡(除非debugger.py
就在專案根目錄底下,容後述),直譯器自然找不到configs
模組。
換句話說,Python 直譯器在執行 import 時,會從sys.path
中,由左至右依序嘗試要 import 的路徑。一般而言,sys.path
的值可以例示如下:
1 | ['', '/usr/local/lib/python38.zip', '/usr/local/lib/python3.8', '/usr/local/lib/python3.8/lib-dynload', '/usr/local/lib/python3.8/site-packages'] |
第一個元素是空字串,代表當前目錄。
兩個幫sys.path
「加料」的方法
因此,若想要正確執行debugger.py
,我們需要把「專案根目錄」的路徑,加入到sys.path
,此時有兩個常見方法。
一、在程式碼中手動加入sys.path
也就是在from configs.config...
之前,先手動加入這段sys.path.append('專案根目錄')
。
這樣的好處是,別人不需要額外設定PYTHONPATH
,因為程式已經幫我們做完了。
而缺點則是——對我來說是一個缺點🐸——太醜了。不僅會降低程式碼的可讀性,且Flake8
也會給出錯誤訊息(E402):
1 | Module level import not at top of file |
不過,在團隊協作時,這樣的做法可以減少大家在「設定未同步」時的潛在問題,所以還是要依不同情境考量,究竟要用哪種方式。
基於協作一致性考慮,本文的案例我仍建議同事採用這個做法。
二、使用PYTHONPATH
儘管如此,有時候我們只是個人開發,或不想在程式碼中直接修改sys.path
,則設定PYTHONPATH
是更常見的做法。
VS Code 設定PYTHONPATH
首先要說明的是,PYTHONPATH
在不同情境會被不同的工具使用,比如 Dockerfile。
而本文只集中在「使用 VS Code」的情境下——究竟要怎麼設定,才能讓 VS Code 取得正確的路徑?
嚴格說,是指 VS Code 的整合命令列中,如何正確套用PYTHONPATH
。好讓 VS Code 在以整合命令列執行程式時,可以正確 import,而不會出現ModuleNotFoundError
。
通常這段設定會放在「專案」下的settings.json
,也就是「專案/.vscode/settings.json
」,你要使用「使用者」設定(全域的settings.json
)也是可以,只是要留意不同專案是否會因此而有不一致的結果。
設定PYTHONPATH
的三種方式
關於 VS Code 加入PYTHONPATH
的settings.json
設定,從以前(至少是 3 年前)到現在,有過方式變遷,我們只需要知道:舊的方法已經不管用。
第一種方式:直接設定 settings.json(已廢棄)
1 | "python.pythonPath": "專案根目錄" |
簡單暴力,透過python.pythonPath
這個 token,直接幫 VS Code 直接指定PYTHONPATH
,但此 token 已經作古(被移除)了,可以不必理會。
第二種方式:透過 env 檔
你先建立 env 檔,讓 VS Code 去讀取它。不用說,env 裡面必須要有PYTHONPATH
這個 key 才行。
通常我們會這樣設定 env 內容:
1 | PYTHONPATH=$(pwd) |
或
1 | PYTHONPATH=. |
可以看出,使用的是「相對路徑」或「環境變數」,兩者異曲同工,實際想指向的,都是專案根目錄。不用絕對路徑,則是為了「方便在不同專案之間套用」。
要特別注意,無論是.
還是$(pwd)
,都是「相對於該 env 檔所在的資料夾」而言。
換句話說,如果你的 env「沒有」放在專案根目錄底下,這個設定就可能會出錯。此時為了避免過度複雜,改用絕對路徑也是可以的。
重要:sys.path 的第一順位值
需要補充說明的是,執行任意 Python 檔案時,Python 直譯器會依序嘗試sys.path
裡的每個值,直到找到你要 import 的模組。而sys.path
的「第一順位值」就是執行檔所在的路徑(資料夾):
By default, the interpreter looks for a module within the current directory. To make the interpreter search in some other directory you just simply have to change the current directory.
換句話說,如果debugger.py
「正好」就在專案根目錄下,那就不需要額外的設定。因此,本文案例之所以會出現ModuleNotFoundError
,也正因debugger.py
不在專案的根目錄裡。
可想而知,把debugger.py
改放在專案根目錄下,就不會有問題了。
但這並不是解決問題的根本方法。換句話說,我們不能只因為「方便」,就輕易改變原本設計好的專案結構。
這樣「妥協」的做法,往往會讓專案結構逐漸變得「缺乏一致性」而難以維護。
settings.json 設定 env 讀取路徑
有了 env 檔案,接著就是要讓 VS Code 去讀取它,settings.json
要加入下列內容:
1 | "python.envFile": "${workspaceFolder}/.env" |
${workspaceFolder}
的意思是「VS Code 當前開啟的專案根目錄」。
你可以把 env 檔放在更深層的資料夾,只要python.envFile
的路徑正確,VS Code 仍然能夠正確讀取它。
但如前所述,env 中PYTHONPATH
的值若為$(pwd)
或.
,那它的「實際路徑」就會隨著 env 所在的路徑而變動,有著不確定性。
所以,我們往往就是把 env 放在專案根目錄。當然,它通常叫「.env
」。
第三種方式:「整合 terminal」設定
一開始看到ModuleNotFoundError
時,我立刻想到的就是第二種設定,趕緊翻出筆記,依樣畫葫蘆。
沒想到,它不 work!
我開啟 VS Code 的整合命令列去執行debugger.py
,發現它依舊找不到configs
的路徑。這就奇了,我之前都是這樣做的啊?
後來想了一下,以前發生問題,主要是 VS Code 在對程式進行靜態分析時,會直接提示找不到路徑,所以我才透過python.envFile
和 env 檔來解決。
而且這次是整合命令列的執行問題,兩者不盡相同。
但無妨,反正問題的本質不變,我們知道其中關鍵,就是PYTHONPATH
設定,所以關鍵字打下去,不意外的——答案就在文件裡!
官方文件
使用「vscode pythonpath」關鍵字,你將輕易找到這篇「Using Python environments in VS Code」。
其中,和本文議題直接相關的,就是最下方的「Use of the PYTHONPATH variable」部分:
The PYTHONPATH environment variable specifies additional locations where the Python interpreter should look for modules. In VS Code, PYTHONPATH can be set through the terminal settings (
terminal.integrated.env.*
) and/or within an.env
file.
本段的最大重點就是:PYTHONPATH
環境變數除了使用 env 檔外,也可以直接由terminal.integrated
這個 token 設定。
看到這裡,整個「故事背景」我們已經了解得差不多了,直接來看設定吧!
為 VS Code 的整合命令列設定PYTHONPATH
設定內容:
1 | "terminal.integrated.env.linux": { |
這個設定允許你,為「VS Code 整合命令列」額外新增環境變數,方便你在命令列中執行程式。
上述的env.linux
可以改為env.osx
或env.windows
,讓你依不同作業系統設定不同變數內容。如果都一致,就可以像文件中那樣,使用萬字元env.*
即可。
因為我只在 Linux VM 上執行,所以只設定了.linux
。
雖然文件表明,你也可以透過.env
設定PYTHONPATH
,但.env
中的PYTHONPATH
,可能只對 linter、formatter 等工具有效,而不會影響 VS Code 的整合命令列。至少我無法透過.env
來解決命令列的執行問題。
特別提醒
terminal.integrated
如它的名稱所述,只對 VS Code 的整合命令列有效:
However, in this case when the extension is performing an action that isn’t routed through the terminal, such as the use of a linter or formatter, then this setting won’t have an effect on module look-up.
舉例而言,如果你自行開一個 terminal 去執行debugger.py
,則還是一樣會出現ModuleNotFoundError
。
如果這對你造成困擾,那麼採用「在程式碼加入sys.path
」的方式,或許才是適合的選擇。
相關文章