完整讲解 · 5 段教学·配 4 道练习题·预计 30 分钟
本页是本章的通读版,可直接读完全部讲解。想动手写代码、跑判分,去 闯关模式。
这一节学的是 枚举类(Enum)——一种用来 代替魔法常量 的语法工具。
Enum 基本用法@unique / auto()== / is / IntEnum实际开发里,我们经常会遇到「几个固定的、有限的取值」这种场景:
最朴素的写法是用一组大写常量:
JAN = 1
FEB = 2
MAR = 3
# ...
NOV = 11
DEC = 12简单快捷,但有三个缺点:
JAN 只是一个 int,谁都可以拿别的整数来冒充:def show_month(m),调用方传进来 m = 99,函数也没办法知道这是不是一个合法的月份。JAN = 100 也不会报错。更好的做法是 定义一个 class,每个常量都是 class 里唯一的实例。
Python 标准库的 enum 模块就是干这个的。看看效果:
from enum import Enum
class Month(Enum):
JAN = 1
FEB = 2
MAR = 3
# ...
DEC = 12
print(Month.JAN) # Month.JAN
print(Month.JAN.name) # 'JAN'
print(Month.JAN.value) # 1跟普通常量比,Enum 的好处:
Month.JAN 的类型就是 Month,传错参数立刻看出来Month.JAN = 999 会直接报错Month() 不会创造新成员,所有成员都是 单例for m in Month: ... 直接拿到所有成员
Enum的成员均为 单例(Singleton),并且 不可实例化、不可更改。
最常见的写法是 继承 Enum 类:
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3Color 本身是一个 类,Color.RED / Color.GREEN / Color.BLUE 是它的三个 成员。
print(Color.RED) # Color.RED ← 默认 repr
print(Color.RED.name) # 'RED' ← 成员名
print(Color.RED.value) # 1 ← 对应的值记住两个属性:
.name —— 成员的 名字(字符串).value —— 成员的 值(任意类型)Enum 是可迭代的:
for c in Color:
print(c.name, c.value)输出:
RED 1
GREEN 2
BLUE 3
也可以用 __members__ 拿到一个 有序映射(包含别名):
for name, member in Color.__members__.items():
print(name, '->', member, '->', member.value)不写 class,直接用 Enum(...) 函数:
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
print(Month.Jan) # Month.Jan
print(Month.Jan.value) # 1,从 1 开始自动分配第一个参数是类名,第二个参数是成员名的可迭代对象。这种写法适合「成员只是一组名字、不在乎具体的值」的场景。
推荐 优先用 class 写法——明确、可读、IDE 提示更友好。函数式写法只在需要动态生成 Enum 时才用。
Enum 成员的值不一定要是数字,字符串、元组、对象都行:
from enum import Enum
class Month(Enum):
Jan = 'January'
Feb = 'February'
Mar = 'March'
# ...
Dec = 'December'
print(Month.Jan) # Month.Jan
print(Month.Jan.name) # 'Jan'
print(Month.Jan.value) # 'January'默认情况下,Enum 允许 多个成员有相同的值——后定义的会变成前面的别名。
from enum import Enum
class Color(Enum):
RED = 1
SCARLET = 1 # 不报错,SCARLET 成了 RED 的别名
print(Color.SCARLET is Color.RED) # True如果你不想这样,加上 @unique 装饰器,就强制每个值唯一,重复时直接抛错:
from enum import Enum, unique
@unique
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
# SCARLET = 1 ← 加上这一行就会 ValueError成员太多、又不在乎具体的值是几?用 auto() 让 Python 自动分配:
from enum import Enum, auto
class Weekday(Enum):
MON = auto()
TUE = auto()
WED = auto()
THU = auto()
FRI = auto()
SAT = auto()
SUN = auto()
print(Weekday.MON.value) # 1
print(Weekday.SUN.value) # 7auto() 默认从 1 开始递增。需要自定义起始值或步长,可以重写 _generate_next_value_ 方法(进阶用法,先跳过)。
如果希望 Enum 成员 同时是 int(可以参与算术运算、可以和 int 比大小),用 IntEnum:
from enum import IntEnum
class Priority(IntEnum):
LOW = 1
MID = 5
HIGH = 10
print(Priority.LOW < Priority.HIGH) # True
print(Priority.MID + 1) # 6StrEnum(Python 3.11+)是同理的字符串版本。
== 和 is普通 Enum 成员既支持 ==(值相等比较)也支持 is(同一对象)。因为 每个成员都是单例,两者结果一致:
from enum import Enum
class User(Enum):
Alice = 98
Bob = 30
Tom = 12
a = User.Alice
b = User.Bob
print(a == b, a == User.Alice) # False True
print(a is b, a is User.Alice) # False True< >普通 Enum 不支持大小比较——成员之间没有「天然顺序」:
from enum import Enum
class User(Enum):
Alice = 98
Bob = 30
try:
print(User.Alice < User.Bob)
except TypeError as e:
print('Error:', e)输出:
Error: '<' not supported between instances of 'User' and 'User'
如果对它们 sorted(...),也会因为同一个原因抛 TypeError。
如果业务上需要比较顺序(比如优先级),用 IntEnum 替代 Enum:
from enum import IntEnum
class User(IntEnum):
Alice = 98
Bob = 30
Tom = 12
# 按 value 排序
for u in sorted(User):
print(u.name)输出:
Tom
Bob
Alice
IntEnum 的成员同时也是 int,所以可以用 < / > / sorted 等等。
总结一下:当你写下任何一段「这个变量只能取下面这几个值」的代码时,就应该考虑 Enum。
Pending / Running / Done / FailedIntEnum,可以排序和比较代替散落的 STATUS_PENDING = 0 这种「字符串/整数魔法值」,让代码更安全、更清晰。
读完了?动手练一遍才算真会。
去闯关模式练习 →