Python Enum 同值别名:定义了三个名字,为什么遍历只有两个成员

前天 3阅读

系统把等待状态从 WAITING 改名为 PENDING,为兼容旧配置,两个名字暂时都保留并使用同一个字符串值。读取旧名字没有报错,可是生成帮助清单时,遍历枚举只出现两个状态。这里不是循环漏读,也不是名字被删除,而是枚举把同值的后定义名称作为别名,普通迭代只列出规范成员。

这个机制适合表示同一业务状态的多个入口名称。如果两个名称本来代表不同状态,仅仅碰巧用了相同编码,别名反而会把设计错误隐藏起来。下面用三个名称、两个值验证区别。把完整代码保存为 demo.py,运行 python demo.py;实验只使用标准库,不读写文件。

Python Enum 同值别名:定义了三个名字,为什么遍历只有两个成员

AI生成概念插图:三个空白名称标签连接到两个状态,其中两个标签指向同一个状态;不是软件界面或运行截图。

from enum import Enum, unique

class State(Enum):
    PENDING = "queued"
    WAITING = "queued"
    DONE = "done"

canonical = [member.name for member in State]
names = list(State.__members__)
aliases = [name for name, member in State.__members__.items()
           if name != member.name]
assert canonical == ["PENDING", "DONE"]
assert names == ["PENDING", "WAITING", "DONE"]
assert aliases == ["WAITING"]
assert State.WAITING is State.PENDING
assert State["WAITING"] is State.PENDING
assert State("queued") is State.PENDING
print("canonical:", canonical)
print("names:", names)
print("aliases:", aliases)
print("lookup name:", State["WAITING"].name)

try:
    @unique
    class StrictState(Enum):
        PENDING = "queued"
        WAITING = "queued"
except ValueError:
    print("duplicate rejected")
else:
    raise AssertionError("duplicate value was accepted")

两个名字指向同一个成员

第一行输出 canonical: ['PENDING', 'DONE'],第二行 names 则列出 PENDING、WAITING、DONE。前者回答有哪些不同成员,后者回答声明了哪些可用名称。给用户生成业务状态下拉框,通常需要前者;检查兼容名称是否仍然受支持,则要读取后者。不能用一个列表同时承担两种职责。

WAITING 与 PENDING 的身份比较为真,说明它们不是两份仅仅值相等的成员。通过方括号查找 WAITING,拿到的仍是 PENDING 成员;通过字符串值 queued 构造成员,也得到同一个对象。因此读取成员的 name 会得到规范名称 PENDING,无法反推出调用者当初用了哪个入口。

如果迁移日志必须区分旧名称和新名称,要在解析之前另存原始字符串。只保存解析后的成员,再在导出时读取 name,会统一成规范名称;这是一次信息收敛,不能要求枚举自己记住每次查找的输入。对外协议究竟传名称还是值,也应该单独约定,避免两端一端按名、一端按值。

允许别名与禁止重复要主动选择

程序中的 aliases 清单通过“声明名称是否等于成员规范名称”筛出 WAITING。它保留的是别名到成员的关系,适合生成弃用提示或者兼容测试。__members__ 是包括别名的只读映射,不要把它当成普通字典,运行期间往里面随意塞新状态。这里也没有依赖后续版本新增的动态别名接口。

末尾的 unique 检查故意定义重复值,类创建时便抛出 ValueError,随后打印 duplicate rejected。这适合业务要求每个名称必须拥有不同值的枚举。若你确实依赖兼容别名,就不应该加这个装饰器后再通过捕获异常掩盖问题;应当把允许重复的名称清楚写进接口说明。

新增状态前可以先回答三个问题:它是新状态还是旧状态的新叫法,旧名称是否需要继续接受,导出是否统一用规范名称。测试同时覆盖名称清单、迭代清单和两条查找路径,比只断言状态值相等更有用。还要保持规范名称定义顺序稳定,因为把旧别名挪到前面,会改变最终显示的 name。

本例只讨论普通 Enum 的相同值别名,不把位标志组合或者整数混合枚举的行为混进来。未知名称会导致 KeyError,未知值则会导致 ValueError;应用可以把它们转换成可理解的输入错误,但不要无条件退回某个默认状态,否则拼写错误也会被当成合法业务状态。

资料核对日期:2026年10月2日(北京时间)。示例在本地 Python 3.12.14 实际运行并通过断言,结果仅对应文中给定输入。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。