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

能否通过__getattribute__覆盖已有属性类型?Pyright报错解析

解决Pyright无法识别自定义__getattribute__返回类型的问题

问题概述

我重写了类的__getattribute__方法,让所有非下划线开头的属性始终返回空字符串,但Pyright静态类型检查无法识别这一逻辑:

  • 访问Model类中声明为int类型的field属性并赋值给str变量时,报错"int" is not assignable to "str"
  • 访问未定义的字段时无类型错误
  • 实际运行中ty.reveal_type确认返回类型为str

代码示例:

import typing as ty
from dataclasses import dataclass


class MetaModel(type):
    def __new__(cls, name, bases, attrs):
        return super().__new__(cls, name, bases, attrs)

    def __getattribute__(cls, name: str, /) -> str:
        if name.startswith("_"):
            return super().__getattribute__(name)
        return ""


class BaseModel(metaclass=MetaModel):
    def __getattribute__(self, name: str) -> str:
        return ""


@dataclass
class Model(BaseModel):
    field: int = 0


if __name__ == "__main__":
    model = Model()
    field1: str = model.field  # "int" is not assignable to "str"
    field2: str = Model.field  # "int" is not assignable to "str"
    field3: str = Model.undefined_field  # 无报错
    ty.reveal_type(field1)  # 输出: str
    ty.reveal_type(field2)  # 输出: str
    ty.reveal_type(field3)  # 输出: str

原因分析

Pyright等静态类型检查器依赖类的显式类型注解和默认属性查找逻辑推断类型,不会执行代码逻辑分析__getattribute__的自定义行为:

  • 对于已声明的属性(如Model.field),会优先使用声明的int类型,忽略__getattribute__的返回类型注解
  • 对于未定义的属性,才会 fallback 到__getattribute__的返回类型

解决方案

方案1:用__getattr__替代__getattribute__(推荐)

__getattr__仅在属性找不到时触发,配合类型注解可让检查器正确识别返回类型。同时保留__getattribute__处理下划线开头的属性:

import typing as ty
from dataclasses import dataclass
from typing import Any


class MetaModel(type):
    def __new__(cls, name, bases, attrs):
        return super().__new__(cls, name, bases, attrs)

    def __getattribute__(cls, name: str, /) -> Any:
        if name.startswith("_"):
            return super().__getattribute__(name)
        raise AttributeError(name)

    def __getattr__(cls, name: str) -> str:
        return ""


class BaseModel(metaclass=MetaModel):
    def __getattribute__(self, name: str) -> Any:
        if name.startswith("_"):
            return super().__getattribute__(name)
        raise AttributeError(name)

    def __getattr__(self, name: str) -> str:
        return ""


@dataclass
class Model(BaseModel):
    field: int = 0


if __name__ == "__main__":
    model = Model()
    field1: str = model.field  # 无类型错误
    field2: str = Model.field  # 无类型错误
    field3: str = Model.undefined_field  # 无类型错误
    ty.reveal_type(field1)  # str
    ty.reveal_type(field2)  # str
    ty.reveal_type(field3)  # str

方案2:强制类型转换或添加类型注解覆盖

如果必须保留__getattribute__,可通过以下方式绕过类型检查:

  • 赋值时用typing.cast强制转换类型:
    from typing import cast
    
    field1: str = cast(str, model.field)
    field2: str = cast(str, Model.field)
    
  • 在BaseModel中添加空实现的__getattr__类型注解,让检查器认为所有属性返回str:
    class BaseModel(metaclass=MetaModel):
        def __getattribute__(self, name: str) -> str:
            return ""
        
        # 告诉类型检查器任意属性返回str
        def __getattr__(self, name: str) -> str:
            ...
    

方案3:用Protocol定义类型行为

通过typing.Protocol定义协议,规定所有非下划线属性返回str,让Model实现该协议:

from typing import Protocol

class StringAttributeProtocol(Protocol):
    def __getattr__(self, name: str) -> str:
        ...

@dataclass
class Model(BaseModel, StringAttributeProtocol):
    field: int = 0

类型检查器会遵循协议的类型定义,忽略原本的属性声明类型。

内容的提问来源于stack exchange,提问作者Alex Filiov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 21:23:26