在 Python 项目里,常量经常从几个字符串、几个整数开始,最后散落成一片 "draft"、1、4、"admin"。Enum 的价值不是“写法更高级”,而是把一组相关常量收进一个类型里,让 IDE、测试、日志和后续重构都更可靠。这个主题覆盖了 Python Enum 的核心用法,以及 IntEnum、IntFlag 在整数兼容和位运算场景下的边界。
常量不是变量名游戏,而是类型边界
最朴素的常量写法通常是这样:
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 成员有两个常用属性:name 和 value。name 是枚举成员名,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 = 1 和 UserRole.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 里会生成适合位组合的值,例如 1、2、4。这比手写 READ = 1、WRITE = 2、EXECUTE = 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 最擅长的是命名、分组、比较和边界表达。把它用在这些地方,代码会安静很多。