如何使通过装饰器注册的类扩展被Intellisense识别?
解决Python动态访问器无法被Intellisense识别的问题
背景
我正在基于Google Earth Engine API开发扩展包geetools,目标是给现有包的对象添加一些对新用户来说重复或复杂的额外功能。由于该API的客户端-服务器交互特性,要保留服务端逻辑就不能用简单的类定义,必须采用扩展方式实现。
问题
扩展功能运行正常,但VSCode的Intellisense无法识别这些功能——悬停时看不到文档和函数原型,没法执行“转到定义”,甚至连参数名称都看不到,导致使用难度极大。
复现示例
以下示例无实际业务意义,但准确复现了实现方式和问题:
from typing import Callable class Toto(object): def __init__(self): self.a = 1 def print(self): print(self.a) def register_class_accessor(klass: type, name: str) -> Callable: """Create an accessor through the provided namespace to a given class. Parameters: klass: The class to set the accessor to. name: The name of the accessor namespace Returns: The accessor function to to the class. """ def decorator(accessor: Callable) -> object: class ClassAccessor: def __init__(self, name: str, accessor: Callable): self.name, self.accessor = name, accessor def __get__(self, obj: object, *args) -> object: return self.accessor(obj) # 检查该类是否已存在同名访问器 if hasattr(klass, name): raise AttributeError(f"Accessor {name} already exists for {klass}") # 给类注册访问器 setattr(klass, name, ClassAccessor(name, accessor)) return accessor return decorator @register_class_accessor(Toto, "tools") class Accessor: """Toolbox for the ``Toto`` class.""" def __init__(self, obj: Toto): """Initialize the Accessor class.""" self._obj = obj def tool_print(self): """Print the object's attribute.""" print(f"tool object: {self._obj.a}")
调用功能正常:
Toto().tools.tool_print() >>>> tool object: 1
但代码智能提示功能失效:
修改方案
要让Intellisense识别动态添加的访问器,需要给静态分析工具提供足够的类型信息,可通过以下几点修改:
给原始类添加访问器类型注解
在Toto类中显式声明tools属性的类型,让静态分析工具明确它的类型是Accessor:class Toto(object): tools: 'Accessor' # 用字符串避免循环引用,也可导入`from __future__ import annotations` def __init__(self): self.a = 1 def print(self): print(self.a)优化装饰器的类型提示
引入泛型类型变量,让类型系统能正确关联原始类和访问器类的类型:from typing import Callable, TypeVar, Generic T = TypeVar('T') AccessorType = TypeVar('AccessorType') def register_class_accessor(klass: type[T], name: str) -> Callable[[type[AccessorType]], type[AccessorType]]: """Create an accessor through the provided namespace to a given class.""" def decorator(accessor: type[AccessorType]) -> type[AccessorType]: class ClassAccessor(Generic[T, AccessorType]): def __init__(self, name: str, accessor: type[AccessorType]): self.name, self.accessor = name, accessor def __get__(self, obj: T, objtype=None) -> AccessorType: return self.accessor(obj) if hasattr(klass, name): raise AttributeError(f"Accessor {name} already exists for {klass}") setattr(klass, name, ClassAccessor(name, accessor)) return accessor return decorator修正访问器类的参数类型
确保Accessor类__init__方法的参数类型与原始类一致(原示例中ee.Array是错误类型,应改为Toto):@register_class_accessor(Toto, "tools") class Accessor: """Toolbox for the ``Toto`` class.""" def __init__(self, obj: Toto): """Initialize the Accessor class.""" self._obj = obj def tool_print(self): """Print the object's attribute.""" print(f"tool object: {self._obj.a}")可选:添加
__class_getitem__提升分析准确性
给ClassAccessor添加类方法,进一步完善静态分析的类型推断:class ClassAccessor(Generic[T, AccessorType]): def __init__(self, name: str, accessor: type[AccessorType]): self.name, self.accessor = name, accessor def __get__(self, obj: T, objtype=None) -> AccessorType: return self.accessor(obj) @classmethod def __class_getitem__(cls, items): return cls
完成以上修改后,VSCode的Intellisense就能正确识别tools属性及其内部方法,正常显示文档、函数原型,并支持“转到定义”操作。
内容的提问来源于stack exchange,提问作者Pierrick Rambaud
相关产品推荐
相关产品推荐

