VSCode中Pylance正确显示descriptor描述符docstring的方法
问题背景
项目开发中需要在大量类中复用user_id字段逻辑,为了避免重复编写property样板代码,选择通过自定义描述符统一封装字段的读写逻辑,减少代码冗余。
但遇到Pylance(VSCode默认Python语言服务)的适配问题:自定义描述符绑定的类属性,悬停时无法像原生property那样正常展示配置的docstring(文档字符串)。
复现代码
class _UserID(object): """ note for test (wont show) """ def __get__(self, obj, _) -> int: """ note for test (wont show) """ return obj._user_id def __set__(self, obj, val: int) -> int: obj._user_id = int(val) if val else 0 return val class UserWithCustomDescriptor(object): user_id = _UserID() def __init__(self) -> None: self._user_id = 0 class UserWithProperty(object): def __init__(self) -> None: self._user_id = 0 @property def user_id(self) -> int: """ note for test """ return self._user_id @user_id.setter def user_id(self, val: int) -> None: self._user_id = int(val) if val else 0 if __name__ == "__main__": user_prop = UserWithProperty() user_prop.user_id = 42 print(f"{user_prop.__class__.__name__} - {user_prop.user_id}") user_desc = UserWithCustomDescriptor() user_desc.user_id = 42 print(f"{user_desc.__class__.__name__} - {user_desc.user_id}")
实际表现
- 鼠标悬停在基于原生
property实现的user_prop.user_id上时,可正常展示配置的docstring,效果参考:
- 鼠标悬停在基于自定义描述符实现的
user_desc.user_id上时,无法展示描述符类或其__get__方法中编写的docstring,效果参考:
已排查尝试
- 查阅过同类问题的公开解答,给出的方案无法解决Pylance下的提示问题
- 对照Python官方描述符文档排查,了解到原生
property会返回携带docstring的实例而非直接返回值,该特性接近问题根源,但未找到适配Pylance的落地方法
问题原因
Pylance基于Pyright实现静态类型分析,对内置property做了专门的适配逻辑,会自动读取property的getter方法docstring作为属性提示。但对于自定义描述符,静态分析阶段无法预判所有运行时行为,因此不会递归读取描述符类或__get__方法的docstring,这是静态检查器的通用设计,并非功能bug。
可行解决方案
方案1:类属性位置直接声明docstring(零依赖,最稳定)
Pylance优先读取类属性赋值语句下紧邻的字符串字面量作为属性docstring,该写法也符合Python官方的类属性文档规范,运行时字符串会自动绑定到属性的__doc__属性,和原生property行为完全对齐:
class UserWithCustomDescriptor(object): user_id = _UserID() """ 用户唯一标识ID,整数类型,空值时默认存0 """ def __init__(self) -> None: self._user_id = 0
该方案不需要修改描述符实现,所有用到该描述符的类可以根据自身场景编写更贴合业务的字段说明,灵活度最高。
方案2:描述符内部统一绑定docstring(适合通用字段)
如果需要所有使用该描述符的类展示统一的docstring,可以通过描述符的__set_name__钩子显式绑定文档,同时补全类访问场景的返回逻辑对齐原生property行为,Pylance可正确识别类型和文档:
from typing import Any, Optional, Type class _UserID(object): """ 用户唯一标识ID,整数类型,空值时默认存0 """ def __set_name__(self, owner: Type[Any], name: str) -> None: self._attr_key = f"_{name}" # 显式绑定doc到描述符实例 self.__doc__ = self.__class__.__doc__ def __get__(self, obj: Optional[object], objtype: Optional[Type[Any]] = None) -> int | "_UserID": # 类访问时返回描述符实例本身,对齐原生property行为 if obj is None: return self return getattr(obj, self._attr_key, 0) def __set__(self, obj: object, val: int) -> None: setattr(obj, self._attr_key, int(val) if val else 0)
该写法下不需要在每个使用类中重复编写docstring,一次定义所有位置都能读到统一的字段说明。
内容的提问来源于stack exchange,提问作者Lumina
相关产品推荐
相关产品推荐

