权限值必须用2的幂次方,因为只有这样每个权限才独占一个二进制位,确保按位与判断准确:如read=1(0b001)、write=2(0b010)、delete=4(0b100),合值7(0b111);若write误设为3(0b011),则用户权限5(0b101)&3=1,错误判定有write权限。

权限值为什么必须用2的幂次方
因为只有这样,每个权限才能独占一个二进制位,避免按位与(&)判断时产生误匹配。比如 READ = 1(0b001)、WRITE = 2(0b010)、DELETE = 4(0b100),三个权限合起来是 7(0b111)。如果误用 READ = 1、WRITE = 3(0b011),那 user_perms & WRITE == 3 就可能在用户只拥有 READ | DELETE(即 5,0b101)时也成立——因为 5 & 3 == 1,但实际没给 WRITE 权限。
常见错误现象:if user_perms & WRITE: 返回 True,但用户根本没被授予该权限。
- 务必用
1 定义权限常量,例如 <code>READ = 1 、<code>WRITE = 1 、<code>EXECUTE = 1 - 不要手写十进制数,除非你确认它是 2 的整数次幂(1, 2, 4, 8, 16…)
- 权限总数别超过平台整数位宽(Python int 无限制,但数据库字段如
TINYINT只有 8 位,最多存 8 个独立权限)
如何正确判断用户是否拥有某权限
核心是使用按位与(&)后与权限值本身比较,而不是只看结果是否非零。因为权限值本身可能不是 1(比如 WRITE = 2),user_perms & WRITE 的结果可能是 2,也可能是 0;直接用 if user_perms & WRITE: 在结果为 2 时成立,这没错,但语义上容易混淆——它其实等价于“是否包含该位”,而更清晰、可读性更强、且兼容未来扩展(比如权限掩码含多个位)的写法是显式比对:
if user_perms & WRITE == WRITE:
不过对单一位权限来说,两种写法等价;但一旦引入组合权限(如 ADMIN = READ | WRITE | DELETE),就必须用 == 判断,否则 user_perms & ADMIN 可能返回非零子集值(比如只含 READ),导致误判。
- 单权限检查:可用
user_perms & READ(简洁),也可用user_perms & READ == READ(明确) - 多权限联合检查(必须全有):用
(user_perms & (READ | WRITE)) == (READ | WRITE) - 禁止用
in或列表遍历判断——那会失去位运算的 O(1) 性能优势
添加、移除、切换权限用什么操作符
添加用按位或(|),移除用按位与加取反(& ~),切换用按位异或(^)。关键点在于:取反操作符 ~ 是对整个整数取反,所以必须配合掩码使用,不能直接 ~WRITE 后就与,否则高位全是 1(Python 中 ~2 是 -3,二进制无限长),导致意外清零其他权限。
正确做法是先构造仅该位为 0、其余位为 1 的掩码,即 ~WRITE & 0xFF(如果权限总和不超过 8 位),但更通用、安全的方式是用 user_perms & ~WRITE ——Python 的 ~ 虽然生成负数,但与整数做 & 时会自动按补码逻辑处理,实际效果正确。验证过:7 & ~2 == 5(0b111 & ~0b010 = 0b101)。
- 添加权限:
user_perms |= WRITE - 移除权限:
user_perms &= ~WRITE - 切换权限:
user_perms ^= WRITE - 批量添加多个:
user_perms |= READ | EXECUTE - 切记不要写
user_perms = user_perms | WRITE——虽然等价,但|=更符合 Python 习惯且原地修改
数据库存储和 ORM 映射要注意什么
权限字段在数据库中应定义为无符号整数类型(如 MySQL 的 INT UNSIGNED 或 TINYINT UNSIGNED),否则负数可能被截断或引发异常。Django 用户若用 models.PositiveIntegerField,SQLAlchemy 用户需显式指定 type_=sa.Integer().with_variant(sa.dialects.mysql.INTEGER(unsigned=True), 'mysql')。
容易被忽略的是迁移兼容性:旧系统可能用字符串逗号分隔("read,write")或 JSON 数组存储权限,迁移到位模式时,必须一次性转换所有用户数据,且应用层要确保新老逻辑不混用——比如不能一边用 user_perms & READ,一边又调用 user.has_perm('read') 做重复校验。
- 初始化用户权限时,用
0表示无权限,不要用None或空字符串 - 查询带权限过滤的用户时,避免在 SQL 层用函数计算(如
BIT_AND),优先在应用层加载后过滤,或用原生 SQL 的WHERE perms & 2 = 2(MySQL) - 日志或调试时,用
bin(user_perms)或自定义格式化函数查看二进制状态,比十进制数字直观得多
权限位设计本身不难,难的是所有人——包括产品、前端、DBA 和后续接手的同事——都理解并遵守“每位独立、只增不删、掩码不变”的约定。一旦有人绕过位操作直接改字段值,整个系统权限就会静默失效。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











