小白学 Python
AI 编程指南
© 2026 小白学 Python · 基于 walter201230/Python 教程
课程目录关于本站联系方式隐私政策GitHub

全部课程

  1. 00为什么学 Python
  2. 01Python 安装与第一个程序
  3. 02基本数据类型与变量
  4. 03List 与 Tuple
  5. 04Dict 与 Set
  6. 05条件与循环
  7. 06函数
  8. 07迭代
  9. 08面向对象
  10. 09模块
  11. 10魔法方法
  12. 11枚举类
  13. 12元类 metaclass
  14. 13线程与进程
  15. 14正则表达式
  16. 15闭包
  17. 16装饰器
  18. 17代码可读性
  19. 18pathlib:现代化路径处理
  20. 19异常处理
  21. 20dataclass
  22. 21上下文管理器(with)
  23. 22async / await
  24. 23pyproject.toml + uv
  25. 24ruff:一站式代码检查
  26. 25pytest:测试框架
  27. 26日志 logging
  28. 27打包发布到 PyPI
  29. 28学完之后

枚举类

完整讲解 · 6 段教学·配 4 道练习题·预计 40 分钟

本页是本章的通读版,可直接读完全部讲解。想动手写代码、跑判分,去 闯关模式。

教学 01 / 06

前言

2019年10月14日16:59:38 看了一下,还有五个章节就修改完基础部分了。

干就完事了。

目录

这一篇的目录如下:

  • 一、枚举类的使用
  • 二、Enum 的源码
  • 三、自定义类型的枚举
  • 四、枚举的比较
教学 02 / 06

一、枚举类的使用

实际开发中,我们离不开定义常量,当我们需要定义常量时,其中一个办法是用大写变量通过整数来定义,例如月份:

Python· 结合前文阅读
JAN = 1
FEB = 2
MAR = 3
...
NOV = 11
DEC = 12

当然这样做简单快捷,缺点是类型是 int ,并且仍然是变量。

那有没有什么好的方法呢?

这时候我们定义一个 class 类型,每个常量都是 class 里面唯一的实例。

正好 Python 提供了 Enum 类来实现这个功能如下:

Python· 结合前文阅读
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

# 遍历枚举类型
for name, member in Month.__members__.items():
    print(name, '---------', member, '----------', member.value)

# 直接引用一个常量
print('\n', Month.Jan)

输出的结果如下:

文本
Jan --------- Month.Jan --------- 1
Feb --------- Month.Feb --------- 2
Mar --------- Month.Mar --------- 3
Apr --------- Month.Apr --------- 4
May --------- Month.May --------- 5
Jun --------- Month.Jun --------- 6
Jul --------- Month.Jul --------- 7
Aug --------- Month.Aug --------- 8
Sep --------- Month.Sep --------- 9
Oct --------- Month.Oct --------- 10
Nov --------- Month.Nov --------- 11
Dec --------- Month.Dec --------- 12

 Month.Jan

我们使用 Enum 来定义了一个枚举类。

上面的代码,我们创建了一个有关月份的枚举类型 Month ,这里要注意的是构造参数,第一个参数 Month 表示的是该枚举类的类名,第二个 tuple 参数,表示的是枚举类的值;当然,枚举类通过 __members__ 遍历它的所有成员的方法。

注意的一点是 , member.value 是自动赋给成员的 int 类型的常量,默认是从 1 开始的。

而且 Enum 的成员均为单例(Singleton),并且不可实例化,不可更改

状态的名字和状态的值

我们想给学习任务标上「待学习」或「已完成」。用枚举把允许的状态列清楚,再分开观察成员名和值:

Python· 可在网页运行
在闯关页练习 →
from enum import Enum


class StudyState(Enum):
    TODO = '待学习'
    DONE = '已完成'


print(StudyState.TODO.name)
print(StudyState.TODO.value)
print(StudyState('已完成') is StudyState.DONE)

输出:

文本
TODO
待学习
True

TODO 是成员的名称,'待学习' 是这里给它安排的值。StudyState('已完成') 按值寻找成员,不是在创建一个任意内容的新状态。

自己试试一个没有定义过的值,观察 ValueError;再比较 StudyState.TODO == '待学习',结果为 False,因为这里的普通 Enum 成员和它的字符串值不是同一个东西。不同枚举类型的比较规则要看对应类型,不从这个例子推导所有枚举都和字符串一样。

本章的源码分析可以帮助理解实现,但完成一个状态选择的小需求后再回看,通常更容易知道自己在追哪一段逻辑。

教学 03 / 06

二、Enum 的源码

通过上面的实例可以知道通过 __members__ 可以遍历枚举类的所有成员。

那有没有想过为什么呢?

当你看到那段代码的时候,有没有想过为什么通过 __members__ 就能遍历枚举类型的所有成员出来?

我们可以先来大致看看 Enum 的源码是如何实现的;

Enum 在模块 enum.py 中,先来看看 Enum 类的片段

Python· 结合前文阅读
class Enum(metaclass=EnumMeta):
    """Generic enumeration.
    Derive from this class to define new enumerations.
    """

可以看到,Enum 是继承元类 EnumMeta 的;再看看 EnumMeta 的相关片段

Python· 结合前文阅读
class EnumMeta(type):
    """Metaclass for Enum"""
    @property
    def __members__(cls):
        """Returns a mapping of member name->value.
        This mapping lists all enum members, including aliases. Note that this
        is a read-only view of the internal mapping.
        """
        return MappingProxyType(cls._member_map_)

首先 __members__ 方法返回的是一个包含一个 Dict 既 Map 的 MappingProxyType,并且通过 @property 将方法 __members__(cls) 的访问方式改变为了变量的的形式,那么就可以直接通过 __members__ 来进行访问了

练习 1 / 4定义一个 Color 枚举类

请用 from ... import 从 enum 模块导入 Enum,然后:

  1. 定义一个继承 Enum 的类 Color,包含三个成员:
    • RED = 1
    • GREEN = 2
    • BLUE = 3
  2. print(Color.RED)

输出应该是:

文本
Color.RED

提示:直接 print 一个 Enum 成员,默认会打出 类名.成员名 这种格式。

💡 提示(3 条)
  1. from enum import Enum

  2. class Color(Enum): 然后在类体里写 RED = 1 等成员

  3. 完整代码:

    Python
    from enum import Enum
    
    class Color(Enum):
        RED = 1
        GREEN = 2
        BLUE = 3
    
    print(Color.RED)
在编辑器里写这题,跑判分、看参考答案去闯关页做这题 →
练习 2 / 4访问 .name 和 .value

继续上一题的 Color 枚举(含 RED=1 / GREEN=2 / BLUE=3),这次:

  1. print(Color.GREEN.name) —— 打印成员名
  2. print(Color.GREEN.value) —— 打印成员值

输出应该是:

文本
GREEN
2
💡 提示(3 条)
  1. .name 是字符串形式的成员名

  2. .value 是定义时给的那个值

  3. 完整代码:

    Python
    from enum import Enum
    
    class Color(Enum):
        RED = 1
        GREEN = 2
        BLUE = 3
    
    print(Color.GREEN.name)
    print(Color.GREEN.value)
在编辑器里写这题,跑判分、看参考答案去闯关页做这题 →
练习 3 / 4遍历 Enum 的所有成员

定义同样的 Color 枚举(RED=1 / GREEN=2 / BLUE=3),然后用 for 循环遍历它,按定义顺序 依次打印每个成员的 .name。

输出应该是:

文本
RED
GREEN
BLUE

提示:for c in Color: 就能拿到每个成员,再用 c.name 取它的名字。

💡 提示(3 条)
  1. Enum 类本身就是可迭代的:for member in Color

  2. 用 c.name 取成员名字符串

  3. 完整代码:

    Python
    from enum import Enum
    
    class Color(Enum):
        RED = 1
        GREEN = 2
        BLUE = 3
    
    for c in Color:
        print(c.name)
在编辑器里写这题,跑判分、看参考答案去闯关页做这题 →
教学 04 / 06

三、自定义类型、@unique、auto()

1、值可以是任意类型

Enum 成员的值不一定要是数字,字符串、元组、对象都行:

Python· 可在网页运行
在闯关页练习 →
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'

2、@unique:禁止重复值

默认情况下,Enum 允许 多个成员有相同的值——后定义的会变成前面的别名。

Python· 可在网页运行
在闯关页练习 →
from enum import Enum

class Color(Enum):
    RED = 1
    SCARLET = 1   # 不报错,SCARLET 成了 RED 的别名

print(Color.SCARLET is Color.RED)   # True

如果你不想这样,加上 @unique 装饰器,就强制每个值唯一,重复时直接抛错:

Python· 结合前文阅读
from enum import Enum, unique

@unique
class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3
    # SCARLET = 1   ← 加上这一行就会 ValueError

3、auto():让 Python 自动给值

成员太多、又不在乎具体的值是几?用 auto() 让 Python 自动分配:

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)   # 7

auto() 默认从 1 开始递增。需要自定义起始值或步长,可以重写 _generate_next_value_ 方法(进阶用法,先跳过)。

4、IntEnum / StrEnum

如果希望 Enum 成员 同时是 int(可以参与算术运算、可以和 int 比大小),用 IntEnum:

Python· 可在网页运行
在闯关页练习 →
from enum import IntEnum

class Priority(IntEnum):
    LOW = 1
    MID = 5
    HIGH = 10

print(Priority.LOW < Priority.HIGH)   # True
print(Priority.MID + 1)               # 6

StrEnum(Python 3.11+)是同理的字符串版本。

练习 4 / 4用 auto() 自动分配值

请用 from ... import 从 enum 模块同时导入 Enum 和 auto,然后:

  1. 定义一个继承 Enum 的类 Weekday,三个成员都用 auto() 自动赋值:
    • MON = auto()
    • TUE = auto()
    • WED = auto()
  2. print(Weekday.MON.value)
  3. print(Weekday.WED.value)

输出应该是:

文本
1
3

提示:auto() 默认从 1 开始,按定义顺序递增。

💡 提示(3 条)
  1. from enum import Enum, auto 一次导入两个名字

  2. 成员定义里直接写 MON = auto()

  3. 完整代码:

    Python
    from enum import Enum, auto
    
    class Weekday(Enum):
        MON = auto()
        TUE = auto()
        WED = auto()
    
    print(Weekday.MON.value)
    print(Weekday.WED.value)
在编辑器里写这题,跑判分、看参考答案去闯关页做这题 →
教学 05 / 06

三、自定义类型的枚举

但有些时候我们需要控制枚举的类型,那么我们可以 Enum 派生出自定义类来满足这种需要。通过修改上面的例子:

Python· 结合前文阅读
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from enum import Enum, unique

Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))


# @unique 装饰器可以帮助我们检查保证没有重复值
@unique
class Month(Enum):
    Jan = 'January'
    Feb = 'February'
    Mar = 'March'
    Apr = 'April'
    May = 'May'
    Jun = 'June'
    Jul = 'July'
    Aug = 'August'
    Sep = 'September '
    Oct = 'October'
    Nov = 'November'
    Dec = 'December'


if __name__ == '__main__':
    print(Month.Jan, '----------',
          Month.Jan.name, '----------', Month.Jan.value)
    for name, member in Month.__members__.items():
        print(name, '----------', member, '----------', member.value)

输出的结果如下:

文本
Month.Jan ---------- Jan ---------- January
Jan ---------- Month.Jan ---------- January
Feb ---------- Month.Feb ---------- February
Mar ---------- Month.Mar ---------- March
Apr ---------- Month.Apr ---------- April
May ---------- Month.May ---------- May
Jun ---------- Month.Jun ---------- June
Jul ---------- Month.Jul ---------- July
Aug ---------- Month.Aug ---------- August
Sep ---------- Month.Sep ---------- September
Oct ---------- Month.Oct ---------- October
Nov ---------- Month.Nov ---------- November
Dec ---------- Month.Dec ---------- December

通过上面的例子,可以知道枚举模块定义了具有迭代 (interator) 和比较(comparison) 功能的枚举类型。 它可以用来为值创建明确定义的符号,而不是使用具体的整数或字符串。

教学 06 / 06

四、枚举的比较

因为枚举成员不是有序的,所以它们只支持通过标识(identity) 和相等性 (equality) 进行比较。下面来看看 == 和 is 的使用:

Python· 结合前文阅读

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from enum import Enum


class User(Enum):
    Twowater = 98
    Liangdianshui = 30
    Tom = 12


Twowater = User.Twowater
Liangdianshui = User.Liangdianshui

print(Twowater == Liangdianshui, Twowater == User.Twowater)
print(Twowater is Liangdianshui, Twowater is User.Twowater)

try:
    print('\n'.join('  ' + s.name for s in sorted(User)))
except TypeError as err:
    print(f' Error : {err}')

输出的结果:

文本

False True
False True
 Error : '<' not supported between instances of 'User' and 'User'

可以看看最后的输出结果,报了个异常,那是因为大于和小于比较运算符引发 TypeError 异常。也就是 Enum 类的枚举是不支持大小运算符的比较的。

那么能不能让枚举类进行大小的比较呢?

当然是可以的,使用 IntEnum 类进行枚举,就支持比较功能。

Python· 可在网页运行
在闯关页练习 →
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import enum


class User(enum.IntEnum):
    Twowater = 98
    Liangdianshui = 30
    Tom = 12


try:
    print('\n'.join(s.name for s in sorted(User)))
except TypeError as err:
    print(f' Error : {err}')

看看输出的结果:

文本
Tom
Liangdianshui
Twowater

通过输出的结果可以看到,枚举类的成员通过其值得大小进行了排序。也就是说可以进行大小的比较。

读完了?动手练一遍才算真会。

去闯关模式练习 →
← 上一章10 · 魔法方法
枚举类
下一章 →12 · 元类 metaclass
本章目录

枚举类

  1. 教学 01前言
  2. 教学 02一、枚举类的使用
  3. 教学 03二、Enum 的源码
  4. 练习 1定义一个 Color 枚举类
  5. 练习 2访问 .name 和 .value
  6. 练习 3遍历 Enum 的所有成员
  7. 教学 04三、自定义类型、@unique、auto()
  8. 练习 4用 auto() 自动分配值
  9. 教学 05三、自定义类型的枚举
  10. 教学 06四、枚举的比较

学习目录