Python 枚举(Enum)使用教程

枚举(Enum)是Python 3.4+版本引入的标准库核心功能,专门用于定义一组语义明确、不可修改、取值受限的命名常量。相比传统靠开发者约定俗成的大写变量(比如MONTH_JAN = 1),它能提供静态检查(避免打错字、传非法值)、语义可读性、遍历/访问规范化等优势,是写生产级代码时的常用工具。


基本枚举定义

方式1:工厂函数快速创建

如果只是简单用一组顺序递增的默认值(从1开始,0需自定义),可以用Enum的工厂函数快速生成:

from enum import Enum

# 参数1:枚举类名
# 参数2:枚举成员的元组(也支持空格分隔的字符串、字典等)
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 
                       'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

这种方式生成的枚举,每个成员有两个固定属性:

  • name:成员的字符串名称(如Month.Jan.name'Jan'
  • value:成员的分配值(如Month.Jan.value1

方式2:自定义枚举类(生产推荐)

更灵活、更易维护的方式是继承Enum基类创建子类,可以搭配装饰器和自定义值使用:

from enum import Enum, unique

# @unique装饰器:强制成员的值唯一,出现重复会直接报错(避免开发时手滑)
@unique
class Weekday(Enum):
    Sun = 0  # 自定义初始值/任意值(不一定是整数)
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

枚举成员的常用访问方式

枚举成员的访问不能直接用==和整数值/字符串比较语义(除非用特殊子类),推荐用以下4种规范化方式:

# 1. 点号访问(最常用)
day1 = Weekday.Mon
print(day1)  # 输出:Weekday.Mon

# 2. 通过成员名称访问(动态场景)
print(Weekday['Tue'])  # 输出:Weekday.Tue

# 3. 通过成员值访问(反向解析)
print(Weekday(1))  # 输出:Weekday.Mon
# 如果值不存在会抛出ValueError,适合校验传入值

# 4. 枚举间直接比较(安全!)
print(day1 == Weekday.Mon)  # True
print(day1 == Weekday.Tue)  # False

枚举的高级实用特性

自动赋值

如果成员值不重要,只需要区分,可以用auto()自动分配连续值(默认从1开始,继承IntFlag/StrEnum等会调整规则):

from enum import Enum, auto

class Color(Enum):
    RED = auto()    # value=1
    GREEN = auto()  # value=2
    BLUE = auto()   # value=3

自定义成员值 + 绑定额外属性/方法

枚举成员的值不一定是整数/字符串,可以是任意类型;同时枚举类可以像普通类一样定义__init__、实例方法、类方法,绑定业务相关的逻辑:

from enum import Enum

class Planet(Enum):
    # 每个成员的值是一个元组,用于__init__的参数
    EARTH = (5.97e24, 6371)
    MARS = (6.39e23, 3389)
    
    # 自动解析元组值,绑定到成员的mass和radius属性
    def __init__(self, mass, radius):
        self.mass = mass  # 质量(kg)
        self.radius = radius  # 半径(km)
    
    # 绑定计算表面重力的实例方法
    @property
    def surface_gravity(self):
        G = 6.673e-11
        return G * self.mass / (self.radius * 1000)**2

# 使用
print(Planet.EARTH.surface_gravity)  # 约9.81 m/s²

遍历枚举成员

可以通过__members__属性遍历所有成员(包括别名?不过用了@unique就没有别名了)

for name, member in Weekday.__members__.items():
    print(f"{name} 代表 {member},数值是 {member.value}")

实际应用示例:身份信息中的性别枚举

传统的代码可能用0/1/2表示性别,但可读性差、容易传错;用枚举就能完全解决:

from enum import Enum, unique

@unique
class Gender(Enum):
    MALE = 0
    FEMALE = 1
    OTHER = 2

class Student:
    def __init__(self, name, gender):
        self.name = name
        # 直接接收枚举成员作为参数
        if not isinstance(gender, Gender):
            raise ValueError("gender必须是Gender枚举的成员")
        self.gender = gender
        
    def __repr__(self):
        # 输出时用name属性,更易读
        return f"Student(name={self.name}, gender={self.gender.name})"

# 正确使用
student = Student("Alice", Gender.FEMALE)
print(student)  # 输出:Student(name=Alice, gender=FEMALE)

# 错误使用会抛异常(安全!)
try:
    invalid_student = Student("Bob", 1)  # 传了整数
except ValueError as e:
    print(e)  # 输出:gender必须是Gender枚举的成员

Python 3.11+ 新增的便捷子类

如果枚举的成员值必须是字符串/整数,或者需要和对应类型直接兼容(比如LogLevel.INFO == "info"直接返回True),可以用3.11+新增的StrEnum/IntEnum/IntFlag等子类:

from enum import StrEnum, IntEnum

# StrEnum:成员值自动为字符串,且和对应字符串直接相等
class LogLevel(StrEnum):
    DEBUG = "debug"
    INFO = "info"
    WARNING = "warning"
print(LogLevel.INFO == "info")  # True(普通Enum做不到)

# IntEnum:成员值自动为整数,且和对应整数直接相等
class StatusCode(IntEnum):
    OK = 200
    NOT_FOUND = 404
print(StatusCode.OK == 200)  # True

最佳实践总结

  1. 必须加@unique:除非有明确需要别名(很少见),否则强制唯一值防止手滑
  2. 优先用自定义子类:相比工厂函数,子类更灵活、易扩展、易文档化
  3. 避免直接用值/字符串比较语义:除非用StrEnum/IntEnum,否则用枚举成员本身比较
  4. 业务逻辑绑定到枚举:比如上面的行星重力、或者状态流转规则,尽量放在枚举类里,提高内聚性
  5. 命名规范:枚举类名用大驼峰(如Weekday),成员名用全大写(如MONDAY),符合Python约定

总结

Python的枚举解决了传统常量定义的三大痛点:

  1. 没有语义安全:打错变量名、传非法值不会立即报错
  2. 没有明确的取值范围:开发者不知道常量可以传哪些值
  3. 没有业务逻辑内聚:相关逻辑分散在各个地方

虽然它的功能不如某些强类型语言(如Java)丰富,但已经足够满足Python开发的大多数场景,是提高代码质量的重要工具。