You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用数据模型对象实现运算符并适配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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 20:43:24