Python 枚举(enum)详解
用枚举取代魔法数字,让代码自己说话
写代码时,我们经常需要定义一组固定的常量——比如星期几、HTTP 状态码、订单状态。传统做法是用大写变量名,但这些变量本质上仍可被修改。Python 3.4 引入了 enum 模块,专门解决这个问题:枚举成员一旦创建就不可变,天然适合定义常量集合。这篇文章从零讲清楚枚举是什么、怎么用,以及在测试开发中的实战场景。
简单来说,枚举(Enumeration)就是一组有名字的、不可变的、唯一的常量集合。每个枚举成员有两面:一个名称(name)用于在代码中引用,一个值(value)用于和外部数据对接。
枚举的核心价值在于:用有意义的符号名替代裸数字或裸字符串。看到 Status.SUCCESS 比看到 1 要清晰得多,这种"魔法数字"正是代码可读性的一大杀手。
Python 3.4 起将 enum 作为标准库内置模块。如果你维护的是 3.4 之前的老项目,可以通过 pip install enum 安装第三方兼容包,用法与标准库基本一致。
💡 小贴士
枚举不只是「定义常量」的工具——它还是一种领域建模手段。当你发现代码里反复出现同一组固定值(比如测试结果 PASS/FAIL/SKIP、接口请求方法 GET/POST/PUT),就该考虑用枚举把它们收拢到一个地方统一管理。
先看我们不用枚举时怎么定义常量:
# 用大写变量名模拟常量STATUS_PENDING = 0STATUS_RUNNING = 1STATUS_PASSED = 2STATUS_FAILED = 3# 看似"常量",实际仍是变量——随时可以被改STATUS_PASSED = 99 # 不会报错,但后果灾难性
这种写法有三个隐患:① 变量可被修改,无法保证不变;② 不同常量之间没有逻辑关联,散落各处难以维护;③ 使用时只能写裸数字,if status == 2 这种魔法数字让后来者完全摸不着头脑。
枚举天然解决了这三个问题:成员不可修改、同组常量绑定在一个类里、访问时用语义清晰的名称。
枚举的定义语法和 class 完全一致,区别在于继承 Enum 基类。类体中每个类属性就是一个枚举成员,左侧是名称,右侧是值。
from enum import Enumclass WeekDay(Enum): Mon = 0 # 星期一 Tue = 1 # 星期二 Wed = 2 # 星期三 Thu = 3 # 星期四 Fri = 4 # 星期五 Sat = 5 # 星期六 Sun = 6 # 星期日
这里 WeekDay.Mon 就是一个枚举成员,它同时携带 name("Mon")和 value(0)。
TypeError: Cannot reassign members
和普通变量不同,枚举成员一旦定义就无法被重新赋值,从根源上杜绝了常量被意外篡改的风险。
04访问枚举成员name / value / 迭代枚举成员有三种常用访问方式:直接取成员、取名称、取值。同时枚举是可迭代的,能像列表一样遍历。
# 直接访问枚举成员print(WeekDay.Mon) # WeekDay.Mon# 获取成员名称print(WeekDay.Mon.name) # Mon# 获取成员值print(WeekDay.Mon.value) # 0# 通过名称访问print(WeekDay["Wed"]) # WeekDay.Wed# 通过值访问print(WeekDay(4)) # WeekDay.Fri
# 遍历所有成员for day in WeekDay: print(f"{day.name} = {day.value}")# 转为列表print(list(WeekDay))
Mon = 0Tue = 1Wed = 2Thu = 3Fri = 4Sat = 5Sun = 6[<WeekDay.Mon: 0>, <WeekDay.Tue: 1>, <WeekDay.Wed: 2>, <WeekDay.Thu: 3>, <WeekDay.Fri: 4>, <WeekDay.Sat: 5>, <WeekDay.Sun: 6>]
🔑 访问方式速查
① WeekDay.Mon → 枚举成员对象② WeekDay.Mon.name → 成员名称字符串③ WeekDay.Mon.value → 成员值④ WeekDay["Tue"] → 按名称取成员⑤ WeekDay(3) → 按值取成员
枚举成员支持同一性比较(is)和等值比较(==),但不支持大小比较(<>)。
# 同一性比较(is)—— 判断是否是同一个对象print(WeekDay.Mon is WeekDay.Mon) # Trueprint(WeekDay.Mon is WeekDay.Tue) # False# 等值比较(==)—— 判断枚举成员是否相同print(WeekDay.Mon == WeekDay.Mon) # Trueprint(WeekDay.Mon == WeekDay.Tue) # False# 名称和值的比较print(WeekDay.Mon.name == "Mon") # Trueprint(WeekDay.Mon.value == 0) # True
WeekDay.Mon < WeekDay.Tue
TypeError: '<' not supported between instances of 'WeekDay' and 'WeekDay'
枚举的设计哲学是「符号优先于数值」——Mon 和 Tue 是不同的符号,比较它们的大小没有语义意义。如果确实需要按值排序,可以比较 .value。
💡 小贴士
在 if / elif 分支判断中,优先用 is 比较枚举成员,语义比 == 更严格——is 表达"是同一个对象",== 表达"值相等",对于枚举来说结果一样,但 is 的意图更清晰。
枚举的成员名称不允许重复(定义重复名称会直接报错),但值是可以重复的——Python 把值相同的成员视为别名,不会报错。多数场景下我们并不希望值重复,这时可以用 @unique 装饰器来强制校验。
class Color(Enum): RED = 1 GREEN = 2 BLUE = 3 CRIMSON = 1 # 值与 RED 相同,成为 RED 的别名print(Color.CRIMSON) # Color.REDprint(Color.CRIMSON is Color.RED) # True
from enum import Enum, unique@uniqueclass Status(Enum): PENDING = 0 RUNNING = 1 PASSED = 2 FAILED = 3 ERROR = 3 # 值重复,@unique 会报错
ValueError: duplicate values found in <enum 'Status'>: ERROR -> FAILED
⚠️ 踩坑经验
值重复时,Python 把后定义的成员当作先定义者的别名。遍历时别名不会出现——list(Color) 只会返回 [RED, GREEN, BLUE],CRIMSON 被隐藏了。如果这不是你想要的行为,一定加上 @unique。
测试工程师最常打交道的固定值集合,莫过于用例状态和优先级。用枚举把它们管理起来,代码可读性和可维护性都会上一个台阶:
from enum import Enum, unique@uniqueclass TestStatus(Enum): """用例执行状态""" SKIP = 0 PASS = 1 FAIL = 2 ERROR = 3 BLOCKED = 4@uniqueclass Priority(Enum): """用例优先级""" LOW = 1 MEDIUM = 2 HIGH = 3 CRITICAL = 4@uniqueclass HttpMethod(Enum): """接口请求方法""" GET = "GET" POST = "POST" PUT = "PUT" DELETE = "DELETE"
# 模拟一个用例执行结果result = { "case_id": "TC_001", "status": TestStatus.PASS, "priority": Priority.HIGH}# 根据状态做不同处理if result["status"] is TestStatus.PASS: print("✅ 用例通过")elif result["status"] is TestStatus.FAIL: print("❌ 用例失败,需要排查")elif result["status"] is TestStatus.SKIP: print("⏭ 用例跳过")elif result["status"] is TestStatus.ERROR: print("💥 用例执行出错")elif result["status"] is TestStatus.BLOCKED: print("🚫 用例被阻塞")# 序列化输出(接口/报告需要原始值)print(f"状态值: {result['status'].value}") # 状态值: 1print(f"状态名: {result['status'].name}") # 状态名: PASS
注意到一个细节:代码中用 TestStatus.PASS 而不是 1 做判断,任何人读到这行代码都能立刻理解语义。需要对接外部系统(数据库、接口)时,用 .value 取出原始值即可。
💡 给测试工程师的建议
1. 接口自动化框架:把 HTTP 方法、Content-Type、认证方式等用枚举管理,避免硬编码字符串拼错2. 测试报告生成:遍历 TestStatus 做统计汇总,比维护一个常量列表更可靠3. 配置反序列化:从 YAML/JSON 读到的值用 EnumClass(value) 还原为枚举成员,后续判断全部走符号名
枚举的核心理念就一句话:用有意义的符号名替代裸数字和裸字符串。它让代码自解释、让常量不可变、让同组值集中管理。下次在代码里写 if status == 1 之前,先问问自己:这个 1 能不能用枚举表达?
Python 学习笔记系列 | 第十四篇