文章目錄
- 🎯 目標:在 VS Code 調試器中自動顯示這些變量信息
- 🔍 原理簡介
- ?? 其他方案的局限性
- ? 方案一:重寫 `__repr__`
- ? 方案二:向 debugpy 注冊自定義變量顯示器(StrPresentationProvider)
- ? 我的方案優勢
- 🛠? 具體實現步驟
- 1. 找到 debugpy 對應的文件目錄
- 對于 Windows 用戶
- 對于 Ubuntu / Linux 用戶
- 2. 修改 `get_variable_details()` 函數
- 📊 工作流程解析
- ?? 注意事項
- 📚 參考文獻
你是否也有這樣的痛點:在 PyCharm 中調試深度學習模型或代碼時,變量區會清晰顯示每個變量的 shape
和類型信息,而在 VS Code 中卻只能看到一團 tensor(...)
?別急,這篇文章帶你一步一步打造 VS Code 的“PyCharm 式調試體驗”。
先看 VS Code 調試效果圖:
🎯 目標:在 VS Code 調試器中自動顯示這些變量信息
- ?
torch.Tensor
: 顯示{Tensor: (3, 4)}
- ?
numpy.ndarray
: 顯示{ndarray: (2, 2)}
- ?
pandas.DataFrame
: 顯示{DataFrame: (5, 3)}
- ?
list
、dict
、set
、tuple
: 顯示長度{list: 3}
,{dict: 3}
,{set: 3}
,{tuple: 3}
🔍 原理簡介
VS Code 的 Python 調試器底層使用的是 debugpy,其中,變量的顯示格式由 pydevd_xml.py
中的 get_variable_details()
函數控制。通過修改該函數邏輯,我們可以為常見的數據結構注入形狀(shape)或長度(len)信息,使其直接顯示在調試面板中。
?? 其他方案的局限性
在社區中也存在一些嘗試解決此問題的方案,但大多存在以下缺陷:
? 方案一:重寫 __repr__
一種直觀的做法是通過自定義 __repr__
方法來改變變量在調試器中的顯示方式【在 VS Code 中調試 Tensor 形狀不顯示的問題及解決方案】。這種方式可以實現變量顯示的定制化,但它 無法影響調試器中內置類型(如 bool
、int
、str
等)的顯示行為。
? 方案二:向 debugpy 注冊自定義變量顯示器(StrPresentationProvider)
另一種方法是利用 debugpy 提供的擴展機制,注冊一個 StrPresentationProvider
,告訴調試器如何渲染特定類型的變量。【在 VS Code 調試器中自動顯示變量形狀和維度信息】【VS Code 中為調試器增強變量顯示:自動顯示張量 Shape、DataFrame 維度和容器長度】。這種方法雖然理論上更優雅,但在實際使用中發現,它會 讀取原始的完整變量內容 來生成字符串表示,這在面對大型數組、DataFrame 或嵌套結構時會導致 嚴重卡頓甚至崩潰,嚴重影響調試體驗。
? 我的方案優勢
我選擇從 debugpy 內部機制入手,通過修改其源碼中的 get_variable_details()
函數,在變量渲染階段注入形狀信息,從而避免了上述方法的性能問題和副作用。
這一改動僅作用于調試器前端顯示層,不會影響程序運行邏輯,也不會因變量過大而造成性能瓶頸。
而且,debugpy 在內部已經對變量內容做了優化處理,只讀取必要的元數據(如 shape、dtype、len)而不加載整個對象內容,因此能保持幾乎與原始 VS Code 調試器相同的響應速度。
🛠? 具體實現步驟
1. 找到 debugpy 對應的文件目錄
根據你使用的編輯器和操作系統,找到對應的文件目錄:
對于 Windows 用戶
如果你使用的是 VS Code 或基于 VS Code 內核的編輯器(例如 Cursor),則路徑通常如下:
- VS Code:
C:\Users\Tang\.vscode\extensions\ms-python.debugpy-2025.10.0-win32-x64\bundled\libs\debugpy\_vendored\pydevd\_pydevd_bundle\pydevd_xml.py
- Cursor:
C:\Users\Tang\.cursor\extensions\ms-python.debugpy-2025.8.0-win32-x64\bundled\libs\debugpy\_vendored\pydevd\_pydevd_bundle\pydevd_xml.py
💡 注意:實際路徑可能會有所不同,請根據實際情況調整。
對于 Ubuntu / Linux 用戶
如果你使用的是 Ubuntu 或其他 Linux 發行版,路徑通常如下:
- VS Code:
~/.vscode-server/extensions/ms-python.debugpy-2025.10.0-linux-x64/bundled/libs/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py
- Cursor:
~/.cursor-server/extensions/ms-python.debugpy-2025.6.0-linux-x64/bundled/libs/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py
💡 注意:我寫的是遠程
remote-ssh
的服務器路徑,本地路徑可能會有所不同比如.cursor-server
換成.cursor
等,請根據實際情況調整。
2. 修改 get_variable_details()
函數
修改之前做好備份
打開 pydevd_xml.py
文件,找到get_variable_details()
函數,完整修改之后如下:
def get_variable_details(val, evaluate_full_value=True, to_string=None, context: Optional[str] = None):""":param context:This is the context in which the variable is being requested. Valid values:"watch","repl","hover","clipboard""""try:# This should be faster than isinstance (but we have to protect against not having a '__class__' attribute).is_exception_on_eval = val.__class__ == ExceptionOnEvaluateexcept:is_exception_on_eval = Falseif is_exception_on_eval:v = val.resultelse:v = val_type, type_name, resolver = get_type(v)type_qualifier = getattr(_type, "__module__", "")if not evaluate_full_value:value = DEFAULT_VALUEelse:try:# 添加形狀信息shape_info = ""try:# 處理 PyTorch Tensorif type_qualifier == "torch" and hasattr_checked(v, 'shape') and hasattr_checked(v, 'dtype'):shape = tuple(v.shape)dtype = str(v.dtype)shape_info = f"{{Tensor: {shape}}} "# 處理 NumPy ndarrayelif type_qualifier == "numpy" and hasattr_checked(v, 'shape') and hasattr_checked(v, 'dtype'):shape = tuple(v.shape)dtype = str(v.dtype)shape_info = f"{{ndarray: {shape}}} "# 處理 Pandas DataFrameelif type_qualifier == "pandas.core.frame" and hasattr_checked(v, 'shape'):shape = tuple(v.shape)shape_info = f"{{DataFrame: {shape}}} "# 處理 Pandas Serieselif type_qualifier == "pandas.core.series" and hasattr_checked(v, 'shape'):shape = tuple(v.shape)dtype = str(v.dtype)shape_info = f"{{Series: {shape}}} "# 處理其他有 shape 屬性的對象elif hasattr_checked(v, 'shape'):shape_info = f"{{{v.shape}}} "# 處理可計數對象elif hasattr_checked(v, '__len__'):try:length = len(v)shape_info = f"{{{type_name}: {length}}} "except:passexcept:passstr_from_provider = _str_from_providers(v, _type, type_name, context)if str_from_provider is not None:value = shape_info + str_from_providerelif to_string is not None:value = shape_info + to_string(v)elif hasattr_checked(v, "__class__"):if v.__class__ == frame_type:value = pydevd_resolver.frameResolver.get_frame_name(v)elif v.__class__ in (list, tuple):if len(v) > 300:value = "%s: %s" % (str(v.__class__), "<Too big to print. Len: %s>" % (len(v),))else:value = "%s: %s" % (str(v.__class__), v)else:try:cName = str(v.__class__)if cName.find(".") != -1:cName = cName.split(".")[-1]elif cName.find("'") != -1: # does not have '.' (could be something like <type 'int'>)cName = cName[cName.index("'") + 1 :]if cName.endswith("'>"):cName = cName[:-2]except:cName = str(v.__class__)value = "%s: %s" % (cName, v)else:value = shape_info + str(v)except:try:value = repr(v)except:value = "Unable to get repr for %s" % v.__class__# fix to work with unicode valuestry:if value.__class__ == bytes:value = value.decode("utf-8", "replace")except TypeError:passreturn type_name, type_qualifier, is_exception_on_eval, resolver, value
具體來說,在 get_variable_details()
函數中添加了1處內容,并修改了3處內容,具體如下:
- 添加了1處內容。
找到if not evaluate_full_value:
這個地方,進行下面的添加:
if not evaluate_full_value:value = DEFAULT_VALUE
else:try:# 添加形狀信息shape_info = ""try:# 處理 PyTorch Tensorif type_qualifier == "torch" and hasattr_checked(v, 'shape') and hasattr_checked(v, 'dtype'):shape = tuple(v.shape)dtype = str(v.dtype)shape_info = f"{{Tensor: {shape}}} "# 處理 NumPy ndarrayelif type_qualifier == "numpy" and hasattr_checked(v, 'shape') and hasattr_checked(v, 'dtype'):shape = tuple(v.shape)dtype = str(v.dtype)shape_info = f"{{ndarray: {shape}}} "# 處理 Pandas DataFrameelif type_qualifier == "pandas.core.frame" and hasattr_checked(v, 'shape'):shape = tuple(v.shape)shape_info = f"{{DataFrame: {shape}}} "# 處理 Pandas Serieselif type_qualifier == "pandas.core.series" and hasattr_checked(v, 'shape'):shape = tuple(v.shape)dtype = str(v.dtype)shape_info = f"{{Series: {shape}}} "# 處理其他有 shape 屬性的對象elif hasattr_checked(v, 'shape'):shape_info = f"{{{v.shape}}} "# 處理可計數對象elif hasattr_checked(v, '__len__'):try:length = len(v)shape_info = f"{{{type_name}: {length}}} "except:passexcept:pass
- 然后在構建最終顯示值時,將
shape_info
插入前面,共3處:
value = str_from_provider
修改如下:
value = shape_info + str_from_provider
value = to_string(v)
修改如下:
value = shape_info + to_string(v)
value = str(v)
修改如下:
value = shape_info + str(v)
📊 工作流程解析
當我們在 VS Code 中啟動調試會話時,整個流程如下:
- VS Code 啟動調試器并加載內置的 debugpy 模塊。
- debugpy 連接到目標 Python 程序并開始監聽斷點。
- 當程序暫停時,debugpy 收集當前作用域內的變量信息。
- 在變量渲染階段,調用
get_variable_details()
函數生成顯示字符串。 - 我們的修改在此處注入形狀信息。
- 最終結果返回給 VS Code 前端展示。
需要注意的是,VS Code 優先使用其自帶的 debugpy,而不是環境中的 pip 安裝版本。因此,我們的修改需針對 VS Code 擴展目錄中的源文件。
?? 注意事項
- VS Code 更新覆蓋修改:每次更新 VS Code 或 Python 擴展后,可能需要重新應用修改。
- 備份原始文件:修改前務必備份原文件,以便恢復或對比。
📚 參考文獻
- 在 VS Code 中調試 Tensor 形狀不顯示的問題及解決方案
- VS Code 中為調試器增強變量顯示:自動顯示張量 Shape、DataFrame 維度和容器長度
- 在 VS Code 調試器中自動顯示變量形狀和維度信息