应使用 flag 而非 int 或 str,因其将权限建模为可读、可组合、支持位运算的枚举,要求值为 2 的幂(如 auto()),避免逻辑错误;校验用 in,赋权用 |,数据库存整数,集成时需手动转换类型。

为什么不用 int 或 str 做权限位,而要用 Flag
因为权限本质是“多个独立开关的组合”,不是单个状态值。用 int 手动位运算容易写错掩码、漏检查、难调试;用 str 则无法做逻辑或/与/异或,也没法快速判断“用户是否有 A 权限且无 B 权限”。Flag 把位运算封装成可读对象,同时保留底层整数行为,是 Python 3.6+ 中最贴合权限建模的原生方案。
常见错误现象:Permission.ADMIN | Permission.USER 返回 Permission.ADMIN|USER(而非一个新枚举实例),但很多人误以为它不可比较或不能存数据库——其实它仍是合法 Flag 成员,只要定义时用了 auto() 或显式 2 的幂次值就支持。
- 必须确保每个成员值是 2 的幂(如 1, 2, 4, 8),否则
in、&等操作会出错 - 不要混用
Enum和Flag:前者不支持位运算,后者要求值可组合 - 数据库存储推荐用整数字段(不是字符串),方便 SQL 层做
WHERE perms & 4这类查询
Flag 枚举怎么定义才不踩坑
核心是值必须互斥且可叠加。Python 不强制校验,全靠人写对。典型反例:READ=1, WRITE=2, DELETE=3 —— 3 不是 2 的幂,DELETE in (READ | WRITE) 会返回 True,逻辑崩坏。
正确做法:
拥有企业网站常用的模块功能:企业简介模块、联系我们模块、新闻(文章)模块、产品模块、图片模块、招聘模块、在线留言、反馈系统、在线交流、友情链接、网站地图、栏目管理、网站碎片、管理员与权限管理等等,所有模块的分类均支持无限级别的分类,可拓展性非常强大。其中包括万能的栏目管理系统、网站碎片管理系统,通过这些系统,可以组合出各种不同的页面和应用。系统带强大灵活的后台管理功能、支持伪静态URL页面功能、自
立即学习“Python免费学习笔记(深入)”;
- 用
auto()最省心:from enum import Flag, auto <p>class Perm(Flag): READ = auto() # 1 WRITE = auto() # 2 EXEC = auto() # 4 ADMIN = auto() # 8
- 显式写 2 的幂也行,但别跳着写(比如
READ=1, WRITE=4),否则中间值可能被意外触发 - 避免定义
NO_PERMISSION = 0:虽然合法,但user_perms == Perm.NO_PERMISSION容易和“没赋值”混淆,建议用None或空集合代替
权限校验时 &、|、in 怎么选
三者语义不同,不能乱换。例如判断“用户是否拥有上传权限”,不是看 user_perms | UploadPerm,而是看 UploadPerm in user_perms 或 user_perms & UploadPerm == UploadPerm。
-
Perm.A in user_perms:最直观,推荐用于单权限检查 -
user_perms & (Perm.A | Perm.B):要提取交集时用,比如“取用户拥有的所有编辑类权限” -
user_perms | Perm.C:赋予权限时用,但注意它返回新Flag实例,不会修改原变量 - 别用
==比较组合权限:user_perms == (Perm.A | Perm.B)要求用户**仅有且必须有这两个**,通常不符合业务场景
和 Django / FastAPI 这类框架怎么集成
关键点是别把 Flag 当黑盒传——它们大多只认基础类型。比如 FastAPI 的 Query 默认转成 str,Django 的 choices 需要元组列表。
- Django model 字段用
PositiveSmallIntegerField,choices可这样生成:choices = [(m.value, m.name) for m in Perm]
- FastAPI 接口接收整数参数后,手动转:
perm = Perm(user_input),并捕获ValueError处理非法值 - 序列化时(如 Pydantic),重写
.dict()或用@validator把Flag转成整数或逗号分隔字符串,前端才好解析 - 别在 ORM 查询里直接用
Perm.X in Model.perms_field:SQLAlchemy 不识别,得写Model.perms_field.op('&')(Perm.X.value) != 0
实际用起来最麻烦的不是定义,是团队里有人偷偷用 int 硬编码权限值,或者前端传个 "read,write" 字符串过来——这些地方没约束,Flag 再规范也白搭。









