用 Python Enum 把常量管起来:从普通枚举到 IntFlag

2026-07-09 32 预计阅读时间: 1 分钟
来源: realpython.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:6 分钟

在 Python 项目里,常量经常从几个字符串、几个整数开始,最后散落成一片 "draft"14"admin"Enum 的价值不是“写法更高级”,而是把一组相关常量收进一个类型里,让 IDE、测试、日志和后续重构都更可靠。这个主题覆盖了 Python Enum 的核心用法,以及 IntEnumIntFlag 在整数兼容和位运算场景下的边界。

常量不是变量名游戏,而是类型边界

最朴素的常量写法通常是这样:

STATUS_DRAFT = "draft"
STATUS_PUBLISHED = "published"
STATUS_ARCHIVED = "archived"

这能跑,但它没有告诉调用方:这些值属于同一个概念,也没有阻止你传入 "publised" 这种拼写错误。Enum 把“可选值集合”变成一个明确类型:

from enum import Enum

class ArticleStatus(Enum):
    DRAFT = "draft"
    PUBLISHED = "published"
    ARCHIVED = "archived"


def can_edit(status: ArticleStatus) -> bool:
    return status in {ArticleStatus.DRAFT, ArticleStatus.ARCHIVED}

print(ArticleStatus.DRAFT.name)   # DRAFT
print(ArticleStatus.DRAFT.value)  # draft
print(can_edit(ArticleStatus.DRAFT))

运行它:

python enum_status.py

这里的关键点是:业务代码比较的是 ArticleStatus.DRAFT,不是裸字符串。字符串仍然可以作为存储、API 或日志中的值存在,但业务层有了一个更硬的边界。

Enum 的几个容易踩坑的细节

Enum 成员有两个常用属性:namevaluename 是枚举成员名,value 是你赋给它的值。

from enum import Enum

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

for color in Color:
    print(color, color.name, color.value)

输出类似:

Color.RED RED 1
Color.GREEN GREEN 2
Color.BLUE BLUE 3

需要注意的是,普通 Enum 成员不会直接等于它的底层值:

print(Color.RED == 1)        # False
print(Color.RED.value == 1)  # True

这通常是好事,因为它避免了不同概念的整数互相混用。例如 Color.RED = 1UserRole.ADMIN = 1 不应该因为底层值都是 1 就被当成同一类东西。

什么时候用 IntEnum

如果你必须和已有系统、数据库字段、老 API 或第三方协议兼容,常量可能已经被定义成整数。这时可以考虑 IntEnum

from enum import IntEnum

class HttpStatus(IntEnum):
    OK = 200
    NOT_FOUND = 404
    SERVER_ERROR = 500

code = HttpStatus.OK

print(code == 200)       # True
print(int(code))         # 200
print(code.name)         # OK

IntEnum 的好处是能像整数一样参与某些比较和序列化流程;代价是类型边界会变软。下面这种情况就更容易发生:

from enum import IntEnum

class Priority(IntEnum):
    LOW = 1
    HIGH = 2

class SwitchState(IntEnum):
    OFF = 0
    ON = 1

print(Priority.LOW == SwitchState.ON)  # True

所以建议是:只有当你确实需要整数兼容时才用 IntEnum。如果只是想给常量分组,普通 Enum 更稳。

IntFlag 适合权限、开关和位掩码

IntFlag 用来表达“多个选项可以组合”的场景,比如权限、功能开关、文件模式。它支持按位或 |、按位与 & 等操作。

下面是一个可直接运行的权限例子:

from enum import IntFlag, auto

class Permission(IntFlag):
    READ = auto()
    WRITE = auto()
    EXECUTE = auto()

user_permission = Permission.READ | Permission.WRITE

print(user_permission)                         # 3,具体显示可能随 Python 版本略有差异
print(bool(user_permission & Permission.READ)) # True
print(bool(user_permission & Permission.EXECUTE)) # False

if Permission.WRITE in user_permission:
    print("user can write")

保存为 permissions.py 后运行:

python permissions.py

auto()IntFlag 里会生成适合位组合的值,例如 124。这比手写 READ = 1WRITE = 2EXECUTE = 4 更不容易出错,尤其是权限项变多以后。

可以这样实践一个小型 API 参数解析:

from enum import IntFlag, auto

class Feature(IntFlag):
    SEARCH = auto()
    EXPORT = auto()
    AUDIT_LOG = auto()


def parse_features(raw: int) -> Feature:
    return Feature(raw)


def enabled(feature_set: Feature, feature: Feature) -> bool:
    return bool(feature_set & feature)

features = parse_features(Feature.SEARCH | Feature.AUDIT_LOG)

print(enabled(features, Feature.SEARCH))    # True
print(enabled(features, Feature.EXPORT))    # False
print(int(features))                        # 可存入数据库或传给旧接口

这里假设外部系统用整数保存功能开关。业务代码内部仍然使用 Feature.SEARCH 这类清晰名称,只有输入输出边界碰整数。

落地时的选择清单

如果你只是给一组状态、类型、角色、颜色、事件名建模,优先用 Enum。它最能保持类型边界,避免裸字符串和裸整数在代码里乱跑。

如果你要兼容已有整数协议,使用 IntEnum,但要意识到它会和普通整数比较相等,也可能让不同枚举类之间发生意外相等。

如果一个值需要表达多个开关的组合,使用 IntFlag。权限、能力、特性开关、位掩码都适合这种模型。

还有两个工程建议:不要为了“看起来统一”把所有常量都改成枚举;也不要在枚举里塞复杂业务逻辑。Enum 最擅长的是命名、分组、比较和边界表达。把它用在这些地方,代码会安静很多。


相关推荐