首页
/ Ruff / ty 规则解析:invalid-enum-member-annotation —— 为什么枚举成员不应携带类型注解

Ruff / ty 规则解析:invalid-enum-member-annotation —— 为什么枚举成员不应携带类型注解

2026-09-08 23:12:38作者:冯梦姬Eddie

本篇文章聚焦 ruff 仓库中新一代类型检查器 ty 的规则 invalid-enum-member-annotation。该规则用于检测 Enum 子类中带有显式类型注解的枚举成员,因为这类注解与 typing spec 的要求相违背、具有误导性。读完本文,你将理解该规则背后的类型系统语义、ty 在代码中的判定逻辑与边界条件,并掌握在实际 Python 代码中正确书写枚举成员的做法。

规则速览

规则对应的官方文档位于 crates/ty_python_semantic/resources/lint_docs/invalid-enum-member-annotation.md,并在 crates/ty/docs/rules.md 中展示。它的核心信息如下:

属性 取值
规则名 invalid-enum-member-annotation
检测内容 对枚举成员(enum members)的显式类型注解
默认级别 warn(来自 crates/ty/docs/rules.md
状态 stable(自 0.0.20 起稳定)
注册位置 crates/ty_python_semantic/src/types/diagnostic.rs

在代码层面,该 lint 由 ty_python_semantic crate 通过 declare_lint! 宏声明为静态实例 INVALID_ENUM_MEMBER_ANNOTATION

declare_lint! {
    #[doc = include_str!("../../resources/lint_docs/invalid-enum-member-annotation.md")]
    pub(crate) static INVALID_ENUM_MEMBER_ANNOTATION = {
        summary: "detects type annotations on enum members",
        status: LintStatus::stable("0.0.20"),
        default_level: Level::Warn,
    }
}

从源码结构可以推断,同一源码文件(diagnostic.rs)内集中声明了 ty 的数十条类型相关 lint,invalid-enum-member-annotationinvalid-context-managerinvalid-exception-caughtinvalid-generic-enum 等规则并列,构成 ty 在类型层面对无效写法的系统性检查。

规则做了什么(What it does)

规则的检测目标十分明确:检查带有显式类型注解的枚举成员。例如下面的 DOG: int = 2 就会命中该规则:

from enum import Enum


class Pet(Enum):
    CAT = 1  # OK
    # enum members should not be annotated
    DOG: int = 2  # error

为什么这是坏味道(Why is this bad?)

typing spec 规定,类型检查器应当为所有枚举成员推断出字面量(literal)类型。显式注解之所以具有误导性,是因为:

  • 注解给出的类型本身是错误的——枚举成员真正的运行时类型是枚举类本身(Pet.DOG 的类型是 Pet,而不是 int);
  • 在 CPython 的 enum 模块中,带注解的赋值(annotation + value)在运行时依然会被当作枚举成员处理,但多余且错误的注解会迷惑代码读者,让人误以为成员的值类型就是被注解的类型。

即:DOG: int = 2 运行时依然是 Pet 的一个合法成员,其 .value == 2,但注解传达的"DOG 是一个 int"这一信息是错的。

ty 在触发该规则时给出的诊断信息同样印证了这一点,完整文案为:

Type annotation on enum member `DOG` is not allowed

并附带指向 typing spec 中 enum members 一节(https://typing.python.org/en/latest/spec/enums.html#enum-members)的参考链接,方便开发者追溯规范原文(诊断文案实现在 crates/ty_python_semantic/src/types/infer/builder.rs)。

正确写法(Examples)

正确的做法是去掉成员上的类型注解,让类型检查器为成员推断其应有的字面量类型:

from enum import Enum


class Pet(Enum):
    CAT = 1
    DOG = 2

判定逻辑与边界条件(源码级解读)

规则的触发点位于类型推断阶段。在 crates/ty_python_semantic/src/types/infer/builder.rs 中,ty 在推进一个带注解赋值语句(annotated assignment)时会执行一系列判断。综合源码注释与相邻条件,可以还原出完整的判定流程:

  1. 必须有值:检查发生在"带声明的赋值"分支中,即 name: annotation = value 这种同时携带注解与值的形式;纯粹的注解声明(没有值)不在此路径触发;
  2. 名称过滤:成员名不能以 __ 开头(跳过 dunder 与私有名),也不能是 _ignore__value__name_ 这些枚举机制内部保留名称;
  3. Final 特例Final不带类型参数(bare Final)是允许的,不会触发;只有 Final 携带类型参数(如 Final[int])时才会被判定为非法;
  4. 可调用对象豁免:若推断出的值类型是 callable 类型,则不触发——因为可调用对象在运行时永远不会成为枚举成员;
  5. 作用域与类判定:仅当当前作用域是 Class 作用域、且最近的封闭类通过继承关系是 Enumis_enum_class_by_inheritance)时才会报告;
  6. 忽略名单:名称不在该枚举类 _ignore_ 机制所忽略的名单中。

上述每一步在 builder.rs 中都以显式条件写出,注释还直接引用了规范:"The typing spec states that enum members should not have explicit type annotations."

通过 mdtest 文件验证边界行为

规则的各种边界行为在 crates/ty_python_semantic/resources/mdtest/enums.md 中有完整的测试用例,这是理解该规则最直观的参考资料。以下测试要点均可在该文件中找到对应示例:

1. 带注解且有值的属性,运行时仍是成员。即使注解不合法,成员身份也不会改变,enum_members() 依然会列出它:

from enum import Enum
from ty_extensions._internal import enum_members

class Answer(Enum):
    YES = 1
    NO = 2

    annotated_member: str = "some value"  # error: [invalid-enum-member-annotation]

# revealed: tuple[Literal["YES"], Literal["NO"], Literal["annotated_member"]]
reveal_type(enum_members(Answer))
reveal_type(Answer.annotated_member)  # revealed: Literal[Answer.annotated_member]

2. 裸 Final 允许,Final[...] 不允许

from enum import Enum
from typing import Final

class Pet2(Enum):
    CAT: Final = 1  # OK,裸 Final 不指定类型
    DOG: Final = 2  # OK

class Pet3(Enum):
    CAT: Final[int] = 1     # error: [invalid-enum-member-annotation]
    DOG: Final[str] = "woof"  # error: [invalid-enum-member-annotation]

3. 各种会被放行、不触发规则的情况

from enum import Enum, member

class Pet4(Enum):
    CAT = member(1)  # OK:用 enum.member 显式声明成员

class Pet5(Enum):
    CAT = 1
    __private: int = 2      # OK:dunder/私有名永远不会是成员
    __module__: str = "my_module"  # OK

class Pet6(Enum):
    CAT = 1
    species: str  # OK:没有值,属于非成员声明

reveal_type(Pet6.species)  # revealed: str
reveal_type(Pet6.CAT.species)  # revealed: str

4. 可调用对象:callable 值运行时不会是枚举成员,因此给它们加注解是安全的(见 enums.md 附近小节),不会触发该 lint。

常见误区与修复建议

综合规则语义与 mdtest 用例,实际编码中需要留意以下几点:

  • 不要把"成员值类型"写在成员注解位置。若希望约束成员值类型,正确途径是利用枚举类上的 _value_ 注解或自定义 __init__/__new__ 来做值校验(这些用法在 crates/ty_python_semantic/resources/mdtest/enums.md 的"Declared _value_ annotation"等章节有系统说明);
  • Final(如 YES: Final = 1)虽然合法,但规范也认为它并无必要;
  • 若确实需要一个"带注解的类型化属性"挂在枚举类上,请使用不带值的纯声明(如 species: str),它会被当作普通类属性而非成员;
  • 需要表达"某成员显式成为成员"时,使用 enum.member(value) 包装即可,这是标准做法。

小结

invalid-enum-member-annotation 是 ty 在枚举类型系统上的一条细致规则:它源于 typing spec 对枚举成员的统一要求——成员的类型由检查器推断为字面量类型,显式注解既错误又具误导性。通过 crates/ty_python_semantic/resources/lint_docs/invalid-enum-member-annotation.md 描述规则语义、crates/ty_python_semantic/src/types/infer/builder.rs 落地判定逻辑、crates/ty_python_semantic/resources/mdtest/enums.md 固化边界行为,ty 保证了"枚举成员不写类型注解"这一约定在代码库中得到一致、可验证的执行。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391