如何用数据模型对象实现运算符并适配Pyright类型检查?
适配Pyright的运算符重载复用方案
你通过描述符模式实现了算术运算符的复用,直接调用foo.__add__(2)能通过Pyright类型检查,但使用运算符语法foo + 1时触发类型错误——这是因为Pyright对特殊方法(如__add__、__mul__)的类型验证逻辑不同于普通属性:它要求特殊方法本身必须是接受两个参数(self和操作数)的可调用对象,而你的Op描述符返回的Apply实例是只接受单个操作数的可调用对象,Pyright无法自动将运算符语法映射到这种间接调用结构上。
下面提供两种适配Pyright的解决方案:
方案一:显式添加特殊方法重载注解
直接在Foo类中为每个运算符特殊方法添加重载注解,再赋值Op实例。Pyright会优先识别这些显式的重载签名,忽略Op的原始类型:
from typing import Callable as Fn, Any, overload import operator class Apply: """Apply an operator to an object.""" def __init__(self, op: Fn[[Any, Any], Any], obj: Any) -> None: self.op = op self.obj = obj @overload def __call__(self, x: int) -> str: ... @overload def __call__(self, x: str) -> int: ... def __call__(self, x: int | str) -> str | int: return self.op(self.obj, x) class Op: """Data model object for an operator.""" def __init__(self, op: Fn[[Any, Any], Any]) -> None: self.op = op def __get__(self, obj: Any, _: Any) -> Apply: return Apply(self.op, obj) class Foo: # 为__add__添加显式重载注解 @overload def __add__(self, x: int) -> str: ... @overload def __add__(self, x: str) -> int: ... __add__ = Op(operator.add) # 为__mul__添加显式重载注解 @overload def __mul__(self, x: str) -> int: ... @overload def __mul__(self, x: int) -> str: ... __mul__ = Op(operator.mul) foo = Foo() a: str = foo.__add__(2) # 正常 b: int = foo.__mul__("2") # 正常 _ = foo + 1 # Pyright无类型错误 _ = foo * "2" # Pyright无类型错误
方案二:元类批量生成带重载的包装方法
如果需要实现多个运算符,手动添加重载注解会重复,可通过元类自动将Op实例替换为带类型注解的包装方法:
from typing import Callable as Fn, Any, overload, Type import operator class Op: """Data model object for an operator.""" def __init__(self, op: Fn[[Any, Any], Any]) -> None: self.op = op class OpMeta(type): def __new__(cls, name: str, bases: tuple[type, ...], attrs: dict[str, Any]) -> Type: # 遍历类属性,替换Op实例为带重载的包装方法 for attr_name, value in list(attrs.items()): if isinstance(value, Op): # 根据你的需求生成对应的重载和实现 @overload def wrapper(self: Any, x: int) -> str: ... @overload def wrapper(self: Any, x: str) -> int: ... def wrapper(self: Any, x: int | str) -> str | int: return value.op(self, x) attrs[attr_name] = wrapper return super().__new__(cls, name, bases, attrs) class Foo(metaclass=OpMeta): __add__ = Op(operator.add) __mul__ = Op(operator.mul) foo = Foo() _ = foo + 1 # Pyright无类型错误 _ = foo * "2" # Pyright无类型错误
原理说明
Pyright对特殊方法的类型检查遵循Python数据模型的规范,要求特殊方法必须是二元可调用(接收self和操作数)。通过显式重载注解或元类生成的包装方法,让Pyright直接识别到符合规范的方法签名,从而正确处理+、*等运算符语法的类型验证。
内容的提问来源于stack exchange,提问作者Thomas Mailund
相关产品推荐
相关产品推荐

