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

如何在Python中为绑定到Protocol的枚举值添加类型提示

解决方案

方案一:放弃Enum,用Final类属性(推荐)

既然你要的是固定键、能通过属性访问、类型安全的结构,没必要硬套Enum。直接用类加Final类型注解,就能在定义阶段触发类型检查,代码还更简洁:

from __future__ import annotations
import typing as t
from typing import Final


class Viewable(t.Protocol):
    def view(self) -> None:
        ...


class MyNumber:
    def __init__(self, value: int):
        self.value = value

    def view(self) -> None:
        print(self.value)


class MyString:
    def __init__(self, value: str):
        self.value = value

    def view(self) -> None:
        print(self.value)


one = MyNumber(1)
two = MyNumber(2)
three = MyString("three")
four = 4


class MyConfig:
    ONE: Final[Viewable] = one
    TWO: Final[Viewable] = two
    THREE: Final[Viewable] = three
    FOUR: Final[Viewable] = four  # <--- mypy直接在这儿报错:int类型不匹配Viewable


if __name__ == "__main__":
    MyConfig.ONE.view()
    MyConfig.TWO.view()
    MyConfig.THREE.view()
    MyConfig.FOUR.view()

优势:

  • 定义阶段直接揪出类型错误,完全符合你的需求
  • 不用给每个值挨个加注解,只需要给类属性统一加Final[Viewable]
  • 访问方式和Enum一模一样(用.点属性),结构更轻量

方案二:自定义Enum元类实现类型检查

如果一定要保留Enum的特性(比如成员是Enum实例、支持枚举的各种特殊方法),可以自定义元类,在枚举创建时做类型验证,同时给mypy提供类型提示:

from __future__ import annotations
import typing as t
from enum import Enum, EnumMeta
from typing import TypeVar, Generic, cast


class Viewable(t.Protocol):
    def view(self) -> None:
        ...


T = TypeVar("T", bound=Viewable)


class TypedEnumMeta(EnumMeta, Generic[T]):
    def __new__(cls, name: str, bases: tuple[type, ...], namespace: dict[str, t.Any]) -> TypedEnumMeta[T]:
        # 运行时验证每个成员的类型
        for key, value in namespace.items():
            if not key.startswith("_"):  # 跳过私有属性
                if not isinstance(value, Viewable):
                    raise TypeError(f"枚举成员 {key} 必须实现Viewable协议,实际类型是 {type(value)}")
        return cast(TypedEnumMeta[T], super().__new__(cls, name, bases, namespace))

    # 给mypy做类型提示,让它知道成员的类型是T
    def __getitem__(self, item: str) -> T:
        ...

    def __getattr__(self, item: str) -> T:
        ...


class MyEnum(Enum, metaclass=TypedEnumMeta[T]):
    ONE = MyNumber(1)
    TWO = MyNumber(2)
    THREE = MyString("three")
    FOUR = 4  # <--- 运行时会抛出TypeError,mypy也会在这儿报错


# 原MyNumber、MyString定义不变
class MyNumber:
    def __init__(self, value: int):
        self.value = value

    def view(self) -> None:
        print(self.value)


class MyString:
    def __init__(self, value: str):
        self.value = value

    def view(self) -> None:
        print(self.value)


if __name__ == "__main__":
    MyEnum.ONE.view()  # 不用写.value,直接调用方法就行
    MyEnum.TWO.view()
    MyEnum.THREE.view()
    MyEnum.FOUR.view()

说明:

  • 自定义元类TypedEnumMeta在枚举创建时(运行时)就会检查每个成员是否符合Viewable
  • 通过__getattr__和__getitem__的类型提示,让mypy在定义阶段就发现类型错误
  • 访问时不用再写.value,直接调用成员的方法就行

为啥原生Enum+Generic不行?

原生Enum的元类会把每个成员包装成Enum实例,mypy只会在你访问.value属性的时候才检查类型,所以错误只会在调用阶段才冒出来。上面的方案要么绕开Enum的包装(方案一),要么通过元类和类型提示提前把错误揪出来(方案二)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 04:35:26