VS Code 透過 settings.json 設定 PYTHONPATH

告別 ModuleNotFoundError

kyo
9 min readFeb 11, 2023

前幾天,同事為專案的局部元件寫了一個偵錯小程式,我們姑且稱為 debugger.py。該程式中會使用到整個專案的共同設定,這些設定則放在專案下的 configs 模組(資料夾)裡,所以需要另外 import:

import os
import sys

from configs.config import TestMode # import 本地的 configs 模組

而專案的結構則是(這裡只凸顯兩者的「 相對層級關係 」,其餘細節省略):

.
├── 元件
│ ├── debugger.py

├── configs
│ ├── config.py

有經驗的你可能不用執行這個小程式就能預料 — 它找不到 configs

不出所料,直接執行之後,會出現錯誤訊息:

這就牽涉到,Python 直譯器在 import 時,究竟「 如何尋找 import 路徑 」議題。

Python import 基礎

可先參考下列三篇文章,建立關於 import 的基礎世界觀:

從上述引用內容可知, debugger.py所以會出現 ModuleNotFoundError,正是因為 from configs.config import TestMode 這段程式碼,是希望從「專案根目錄」的角度出發去 import。

但在沒有額外設定的情況下,專案根目錄並不在 sys.path裡--除非 debugger.py就在專案根目錄底下(容後述)。所以自然就找不到 configs 模組。

兩個幫sys.path「加料」的方法

因此,若想要正確執行 debugger.py,我們需要把「專案根目錄」的路徑,加入到 sys.path ,此時有兩個常見方法。

一、在程式碼中手動加入sys.path

也就是在from configs.config...之前,先手動加入這段sys.path.append('專案根目錄')

這樣的好處是,別人不需要額外設定PYTHONPATH,因為程式已經幫我們做完了。

而缺點則是 — — 對我來說是一個缺點🐸 — — 太醜了。不僅會降低程式碼的可讀性,且Flake8也會給出錯誤訊息(E402)

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 加入 PYTHONPATHsettings.json 設定,從以前(至少是 3 年前)到現在,有過方式變遷,我們只需要知道:舊的方法已經不管用。

第一種方式:直接設定 settings.json(已廢棄)

"python.pythonPath": "專案根目錄"

簡單暴力,透過 python.pythonPath這個 token,直接幫 VS Code 直接指定 PYTHONPATH ,但此 token 已經作古(被移除)了,可以不必理會。

第二種方式:透過 env 檔

你先建立 env 檔,讓 VS Code 去讀取它。不用說,env 裡面必須要有 PYTHONPATH 這個 key 才行。

通常我們會這樣設定 env 內容:

PYTHONPATH=$(pwd)

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 要加入下列內容:

"python.envFile": "${workspaceFolder}/.env"

${workspaceFolder} 的意思是「VS Code 當前開啟的專案根目錄」。

${workspaceFolder}的意思是「VS Code 當前開啟的專案根目錄」。

你可以把 env 檔放在更深層的資料夾,只要python.envFile的路徑正確,VS Code 仍然能夠正確讀取它。

但如前所述,env 中PYTHONOPATH的值若為$(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

為 VS Code 的整合命令列設定PYTHONPATH

設定內容:

"terminal.integrated.env.linux": {
"PYTHONPATH": "${workspaceFolder}"
},

這個設定允許你,為「VS Code 整合命令列」額外新增環境變數,方便你在命令列中執行程式。

上述的env.linux可以改為.osx.windows,讓你依不同作業系統,分別設定不同變數內容。如果其實都一致,就可以像檔案中那樣,使用萬字元*即可。

因為我只在 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 」的方式,或許才是適合的選擇。

Originally published at https://blog.kyomind.tw on February 11, 2023.

--

--