让 VSCode 调试器像 PyCharm 一样显示 Tensor Shape、变量形状、变量长度、维度信息

文章目录

    • [🎯 目标:在 VS Code 调试器中自动显示这些变量信息](#🎯 目标:在 VS Code 调试器中自动显示这些变量信息)
    • [🔍 原理简介](#🔍 原理简介)
      • [⚠️ 其他方案的局限性](#⚠️ 其他方案的局限性)
        • [❌ 方案一:重写 `repr`](#❌ 方案一:重写 __repr__)
        • [❌ 方案二:向 debugpy 注册自定义变量显示器(StrPresentationProvider)](#❌ 方案二:向 debugpy 注册自定义变量显示器(StrPresentationProvider))
    • [✅ 我的方案优势](#✅ 我的方案优势)
    • [🛠️ 具体实现步骤](#🛠️ 具体实现步骤)
      • [1. 找到 debugpy 对应的文件目录](#1. 找到 debugpy 对应的文件目录)
        • [对于 Windows 用户](#对于 Windows 用户)
        • [对于 Ubuntu / Linux 用户](#对于 Ubuntu / Linux 用户)
      • [2. 修改 `get_variable_details()` 函数](#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)}
  • listdictsettuple: 显示长度 {list: 3}{dict: 3}{set: 3}{tuple: 3}

为此,你可以使用以下代码片段进行验证调试效果(对比修改前后的差异):

python 复制代码
import torch
import numpy as np
import pandas as pd

# 字符串类型赋值
string_data = "这是一个字符串类型的数据"

# Tensor 类型赋值(使用 PyTorch)
tensor_data = torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.float32)

# numpy 数组类型赋值
numpy_data = np.array([[7, 8, 9], [10, 11, 12]], dtype=np.float64)

# 集合类型赋值
set_data = {1, 2, 3, 4, 4}  # 集合会自动去重

# 列表类型赋值
list_data = [13, 14, 15, 16]

# 字典类型赋值
dict_data = {
    "key1": "value1",
    "key2": 2,
    "key3": [20, 21, 22]
}

# 元组类型赋值
tuple_data = (23, 24, 25)

# DataFrame 类型赋值(使用 pandas)
dataframe_data = pd.DataFrame({
    'col1': [26, 27],
    'col2': ['a', 'b']
})

# 打印各类型数据,方便查看
print("字符串数据:")
print(string_data)
print("\nTensor 数据:")
print(tensor_data)
print("\nnumpy 数据:")
print(numpy_data)
print("\n集合数据:")
print(set_data)
print("\n列表数据:")
print(list_data)
print("\n字典数据:")
print(dict_data)
print("\n元组数据:")
print(tuple_data)
print("\nDataFrame 数据:")
print(dataframe_data)

🔍 原理简介

VS Code 的 Python 调试器底层使用的是 debugpy,其中,变量的显示格式由 pydevd_xml.py 中的 get_variable_details() 函数控制。通过修改该函数逻辑,我们可以为常见的数据结构注入形状(shape)或长度(len)信息,使其直接显示在调试面板中。

⚠️ 其他方案的局限性

在社区中也存在一些尝试解决此问题的方案,但大多存在以下缺陷:

❌ 方案一:重写 __repr__

一种直观的做法是通过自定义 __repr__ 方法来改变变量在调试器中的显示方式【在 VS Code 中调试 Tensor 形状不显示的问题及解决方案】。这种方式可以实现变量显示的定制化,但它 无法影响调试器中内置类型(如 boolintstr 等)的显示行为

❌ 方案二:向 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:
bash 复制代码
C:\Users\Tang\.vscode\extensions\ms-python.debugpy-2025.10.0-win32-x64\bundled\libs\debugpy\_vendored\pydevd\_pydevd_bundle\pydevd_xml.py
  • Cursor:
bash 复制代码
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:
bash 复制代码
~/.vscode-server/extensions/ms-python.debugpy-2025.10.0-linux-x64/bundled/libs/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py
  • Cursor:
bash 复制代码
~/.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()函数,完整修改之后如下:

python 复制代码
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__ == ExceptionOnEvaluate
    except:
        is_exception_on_eval = False

    if is_exception_on_eval:
        v = val.result
    else:
        v = val

    _type, type_name, resolver = get_type(v)
    type_qualifier = getattr(_type, "__module__", "")
    if not evaluate_full_value:
        value = DEFAULT_VALUE
    else:
        try:
            # 添加形状信息
            shape_info = ""
            try:
                # 处理 PyTorch Tensor
                if 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 ndarray
                elif 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 DataFrame
                elif type_qualifier == "pandas.core.frame" and hasattr_checked(v, 'shape'):
                    shape = tuple(v.shape)
                    shape_info = f"{{DataFrame: {shape}}} "
                # 处理 Pandas Series
                elif 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)
                        # 对于字符串类型,只显示 {str} 而不显示长度
                        if type_name == "str":
                            shape_info = f"{{{type_name}}} "
                        else:
                            shape_info = f"{{{type_name}: {length}}} "
                    except:
                        pass
            except:
                pass
            
            str_from_provider = _str_from_providers(v, _type, type_name, context)
            if str_from_provider is not None:
                value = shape_info + str_from_provider

            elif 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 values
    try:
        if value.__class__ == bytes:
            value = value.decode("utf-8", "replace")
    except TypeError:
        pass

    return type_name, type_qualifier, is_exception_on_eval, resolver, value

具体来说,在 get_variable_details() 函数中添加了1处内容,并修改了3处内容,具体如下:

  1. 添加了1处内容。
    找到if not evaluate_full_value:这个地方,进行下面的添加:
python 复制代码
if not evaluate_full_value:
        value = DEFAULT_VALUE
else:
    try:
        # 添加形状信息
        shape_info = ""
        try:
            # 处理 PyTorch Tensor
            if 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 ndarray
            elif 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 DataFrame
            elif type_qualifier == "pandas.core.frame" and hasattr_checked(v, 'shape'):
                shape = tuple(v.shape)
                shape_info = f"{{DataFrame: {shape}}} "
            # 处理 Pandas Series
            elif 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:
                    pass
        except:
            pass
  1. 然后在构建最终显示值时,将 shape_info 插入前面,共3处:

value = str_from_provider修改如下:

python 复制代码
value = shape_info + str_from_provider

value = to_string(v)修改如下:

python 复制代码
value = shape_info + to_string(v)

value = str(v)修改如下:

python 复制代码
value = shape_info + str(v)

📊 工作流程解析

当我们在 VS Code 中启动调试会话时,整个流程如下:

  1. VS Code 启动调试器并加载内置的 debugpy 模块。
  2. debugpy 连接到目标 Python 程序并开始监听断点。
  3. 当程序暂停时,debugpy 收集当前作用域内的变量信息。
  4. 在变量渲染阶段,调用 get_variable_details() 函数生成显示字符串。
  5. 我们的修改在此处注入形状信息。
  6. 最终结果返回给 VS Code 前端展示。

需要注意的是,VS Code 优先使用其自带的 debugpy,而不是环境中的 pip 安装版本。因此,我们的修改需针对 VS Code 扩展目录中的源文件。


⚠️ 注意事项

  1. VS Code 更新覆盖修改:每次更新 VS Code 或 Python 扩展后,可能需要重新应用修改。
  2. 备份原始文件:修改前务必备份原文件,以便恢复或对比。

📚 参考文献

  1. 在 VS Code 中调试 Tensor 形状不显示的问题及解决方案
  2. VS Code 中为调试器增强变量显示:自动显示张量 Shape、DataFrame 维度和容器长度
  3. 在 VS Code 调试器中自动显示变量形状和维度信息
相关推荐
海棠AI实验室13 小时前
第七章 断点调试:VSCode 调试全流程(含常见坑)
ide·vscode·编辑器
毕设源码-郭学长13 小时前
【开题答辩全过程】以 基于python电商商城系统为例,包含答辩的问题和答案
开发语言·python
black0moonlight13 小时前
win11 isaacsim 5.1.0 和lab配置
python
知乎的哥廷根数学学派13 小时前
基于多尺度注意力机制融合连续小波变换与原型网络的滚动轴承小样本故障诊断方法(Pytorch)
网络·人工智能·pytorch·python·深度学习·算法·机器学习
网安CILLE14 小时前
PHP四大输出语句
linux·开发语言·python·web安全·网络安全·系统安全·php
jjjddfvv14 小时前
超级简单启动llamafactory!
windows·python·深度学习·神经网络·微调·audiolm·llamafactory
A先生的AI之旅14 小时前
2025顶会TimeDRT快速解读
人工智能·pytorch·python·深度学习·机器学习
程序员小远14 小时前
完整的项目测试方案流程
自动化测试·软件测试·python·功能测试·测试工具·职场和发展·测试用例
程序猿阿伟14 小时前
《量子算法开发实战手册:Python全栈能力的落地指南》
python·算法·量子计算