查看: 122|回复: 3

Python运算符优先级记忆与位运算比较括号纠错

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
Python表达式的结果并不总按从左到右的直觉执行。原文给出几个典型表达式:2 + 3 * 4 得14;10 - 5 - 2 得3;2 ** 3 ** 2 得512;not True and False or True 得True;3 > 2 == True 得True;10 / 2 * 5 得25.0。它们的差异来自运算符优先级、结合性以及比较链式规则。下面把优先级表、结合性、必须加括号的场景和排查工具串起来。

先看优先级总表,按原文从高到低排列:
  1. Python 运算符优先级(从高到低)
  2. 1  (expr...) [expr...] {key: value...} f(arg...) x[index] x[index:index] x.attr
  3. 2  **
  4. 3  +x, -x, ~x
  5. 4  *, /, //, %
  6. 5  +, -
  7. 6  <<, >>
  8. 7  &
  9. 8  ^
  10. 9  |
  11. 10 ==, !=, >, <, >=, <=, is, is not, in, not in
  12. 11 not
  13. 12 and
  14. 13 or
  15. 14 if-else
  16. 15 lambda
  17. 16 =, +=, -=, *=, /= 等赋值运算符
  18. 17 yield
复制代码

用代码验证关键优先级:
  1. print(2 + 3 * 4)        # 14
  2. print((2 + 3) * 4)      # 20
  3. print(-3 ** 2)          # -9
  4. print((-3) ** 2)        # 9
  5. print(2 ** 3 ** 2)      # 512
  6. print((2 ** 3) ** 2)    # 64
  7. print(3 > 2 == True)    # True,等价于 (3 > 2) and (2 == True)
  8. print(10 / 2 * 5)       # 25.0
  9. print(10 / (2 * 5))     # 1.0
  10. print(not True and False or True)  # True
复制代码
这里的几个事实要记牢:-3 ** 2 先算幂再取负;2 ** 3 ** 2 是右结合,先算3**2再算2**9;3 > 2 == True 不是(3 > 2) == True,而是链式比较,等价于(3 > 2) and (2 == True);10 / 2 * 5 中乘除同级,按从左向右计算。

结合性方面,大多数运算符左结合,例如10 - 5 - 2得3,100 / 10 / 2得5.0,10 + 20 - 5得25。右结合的特殊运算符包括幂运算**、一元运算符、赋值运算符和条件表达式。赋值语句a = b = c = 10等价于a = (b = (c = 10))。条件表达式嵌套虽可运行,但可读性差,不适合硬写。

比较运算符还有链式特性:a < b < c等价于(a < b) and (b < c),不是(a < b) < c。1 < 3 < 5为True,1 < 3 > 2也为True。比较运算符优先级高于and,所以1 < 3 and 5 > 2等价于(1 < 3) and (5 > 2)。

短路求值和优先级要分开理解:优先级决定哪些操作数绑定到哪个运算符,短路求值决定哪些部分实际执行。原文用show函数演示:
  1. def show(v):
  2.     print(f'计算了 show({v})')
  3.     return v
  4. print('True or False and show(1):')
  5. result = True or False and show(1)
  6. print(f'结果: {result}')
  7. print('(True or False) and show(1):')
  8. result = (True or False) and show(1)
  9. print(f'结果: {result}')
  10. print('show(0) or show(1) and show(2)')
  11. result = show(0) or show(1) and show(2)
  12. print(f'结果: {result}')
复制代码
第一段中and优先级高于or,但True or ...已经短路,show(1)不会执行;第二段加了括号,show(1)会执行;第三段等价于show(0) or (show(1) and show(2)),show(0)为假后继续计算右侧,show(1)为真时还要计算show(2)来确定and的结果。

记忆时可以分层:访问优先,一元优先,算术优先,移位和位运算,比较和成员,逻辑not/and/or,赋值最后。原文口诀是:括号函数最优先,幂和正负跟后边,乘除整除与取模,加减移位居中间,按位与异或位或,比较成员一条线,非与或来再赋值,lambda最后站一边。口诀用于辅助记忆,遇到复杂表达式仍应加括号。

必须加括号的场景不是风格问题,而是正确性问题。第一类是位运算参与逻辑判断:flags & MASK == MASK等价于flags & (MASK == MASK),因为==优先级高于&,这几乎总是错的;正确写法是(flags & MASK) == MASK。第二类是移位参与算术运算:1 << 2 + 3等价于1 << (2 + 3),结果是32;若想先移位再加3,应写(1 << 2) + 3,结果是7。第三类是not作用于复杂表达式:not a > 0 and b > 0等价于(not (a > 0)) and (b > 0);若想表达两者不都大于0,应写not (a > 0 and b > 0)。第四类是复合赋值与表达式混用,可先算temp再赋给多个变量。
  1. flags = 0b1010
  2. MASK = 0b1000
  3. if flags & MASK == MASK:
  4.     print('可能误判')
  5. if (flags & MASK) == MASK:
  6.     print('匹配')
  7. result = 1 << 2 + 3       # 32
  8. result = (1 << 2) + 3     # 7
  9. if not a > 0 and b > 0:
  10.     pass
  11. if not (a > 0 and b > 0):
  12.     pass
复制代码
真实案例之一是权限位检查。下面这个函数有bug:user_permissions & required == required等价于user_permissions & (required == required),进一步变成user_permissions & True,也就是user_permissions & 1。修复方式就是给位运算加括号。
  1. def has_permission_bug(user_permissions, required):
  2.     return user_permissions & required == required
  3. def has_permission_fixed(user_permissions, required):
  4.     return (user_permissions & required) == required
  5. READ = 0b001
  6. WRITE = 0b010
  7. EXECUTE = 0b100
  8. user_perm = READ | WRITE
  9. print(has_permission_bug(user_perm, WRITE))
  10. print(has_permission_fixed(user_perm, WRITE))
  11. print(has_permission_fixed(user_perm, EXECUTE))
复制代码
另一个常见错误是not的范围。is_active = True、is_admin = False时,not is_admin and is_active等价于(not is_admin) and is_active,碰巧结果可能正确;若要表达不是同时为管理员和活跃用户,必须写成not (is_admin and is_active)。
  1. is_active = True
  2. is_admin = False
  3. if not is_admin and is_active:
  4.     print('可以操作')
  5. if not (is_admin and is_active):
  6.     print('不是同时为管理员和活跃用户')
复制代码
海象运算符:=的优先级很低,几乎总是需要括号。if n := len(data) > 0会被解析成n := (len(data) > 0),n是布尔值而不是长度;正确写法是if (n := len(data)) > 0。result = (x := 10) + 5会得到15,同时x为10。
  1. data = [1, 2, 3]
  2. if (n := len(data)) > 0:
  3.     print(f'数据长度为{n}')
  4. result = (x := 10) + 5
  5. print(result, x)
复制代码
要系统检查优先级,可以写一个测试器,把容易出错的表达式和期望结果列出来:
  1. class PrecedenceTester:
  2.     def __init__(self):
  3.         self.score = 0
  4.         self.total = 0
  5.     def test_question(self, expression, expected, explanation=''):
  6.         self.total += 1
  7.         try:
  8.             actual = eval(expression)
  9.             if actual == expected:
  10.                 self.score += 1
  11.                 print(f'第{self.total}题 正确: {expression} = {actual}')
  12.             else:
  13.                 print(f'第{self.total}题 错误: {expression}')
  14.                 print(f'  直觉: {expected} 实际: {actual}')
  15.                 if explanation:
  16.                     print(f'  解释: {explanation}')
  17.         except Exception as e:
  18.             print(f'第{self.total}题 异常: {e}')
  19.     def run_all_tests(self):
  20.         self.test_question('2 + 3 * 4', 14, '乘法优先级高于加法')
  21.         self.test_question('10 - 5 - 2', 3, '减法从左向右结合')
  22.         self.test_question('2 ** 3 ** 2', 512, '幂运算从右向左结合')
  23.         self.test_question('-3 ** 2', -9, '幂运算优先级高于一元负号')
  24.         self.test_question('10 / 2 * 5', 25.0, '乘除同级,从左向右')
  25.         self.test_question('1 << 2 + 3', 32, '加法优先级高于左移')
  26.         self.test_question('(1 << 2) + 3', 7, '括号改变优先级')
  27.         self.test_question('not True and False', False, 'not > and')
  28.         self.test_question('True or False and False', True, 'and > or')
  29.         self.test_question('3 > 2 == True', True, '链式比较')
  30.         print(f'总计: {self.total}题, 正确: {self.score}题')
  31. tester = PrecedenceTester()
  32. tester.run_all_tests()
复制代码
开发工具也能帮忙。现代IDE如PyCharm、VS Code + Pylance会对flags & MASK == MASK这类写法给出警告,提示等值判断和位运算混淆,建议改成(result = (flags & MASK) == MASK)。进一步可以用AST查看Python如何解析表达式:
  1. import ast
  2. def show_precedence(expr_str):
  3.     print(f'表达式: {expr_str}')
  4.     try:
  5.         tree = ast.parse(expr_str, mode='eval')
  6.         print(ast.dump(tree, indent=2))
  7.     except SyntaxError as e:
  8.         print(f'语法错误: {e}')
  9. show_precedence('2 + 3 * 4')
  10. show_precedence('not True and False')
  11. show_precedence('1 << 2 + 3')
  12. show_precedence('flags & MASK == MASK')
复制代码
总结一下:优先级表从括号、幂、一元、乘除、加减、移位、位运算、比较、not、and、or到赋值;大多数运算符左结合,幂、一元和赋值等右结合;比较有链式规则;位运算加比较、not加复杂表达式、海象运算符是必须加括号的高风险场景。对求值顺序不确定时,加括号或拆成多步,比事后调试更省成本。
回复

使用道具 举报

发表于 半小时前 | 显示全部楼层

Re: Python运算符优先级记忆与位运算比较括号纠错

整理得很系统,把优先级、结合性、比较链式、短路求值和必须加括号的场景串在一起,比单独背表实用多了。尤其是 flags & MASK == MASK 和 1
回复 支持 反对

使用道具 举报

发表于 半小时前 | 显示全部楼层

Re: Python运算符优先级记忆与位运算比较括号纠错

楼主这篇整理得很系统,尤其是把优先级表、结合性和必须加括号的场景串起来讲,比单纯背表实用多了。几个点我印象比较深: 一是 `flags & MASK == MASK` 这个坑太真实了,以前写权限判断时确实容易想当然,结果等价于 `flags & (MASK == MASK)`,变成和布尔值做位运算,逻辑直接跑偏。后面那个权限位检查的例子虽然正文最后被截断了,但光看前面已经能明白问题所在,实际项目里这种 bug 还挺隐蔽的。 二是 `3 > 2 == True` 的链式比较解释得很清楚,我之前一直按 `(3 > 2) == True` 去理解,虽然碰巧结果一样,但底层规则完全不是那回事,换成其他值就可能出错。 三是短路求值那段用 `show` 函数演示很直观,`True or False and show(1)` 和加括号后的执行差异一目了然,优先级和短路确实是两个层面的事,分开理解就不容易混。 口诀里“比较成员一条线”和“按位与异或位或”这两句我觉得挺顺口的,能帮我在写位运算时多停一下想想要不要加括号。总体来说,我现在的习惯就是复杂表达式宁可多加括号,可读性和正确性都更有保障。 另外帖子里最后那个 `def` 好像没贴完整,如果方便的话可以把权限检查的完整函数和修复版本补上,这样对照着看会更直观。
回复 支持 反对

使用道具 举报

发表于 半小时前 | 显示全部楼层

Re: Python运算符优先级记忆与位运算比较括号纠错

这帖把优先级、结合性、短路求值和必须加括号的场景串得很清楚,尤其是 flags & MASK == MASK 和 1 2 == True 这类表达式看起来像先算左边再比较,实际是链式比较,复杂时还是显式写 and 或加括号更稳。 短路示例用 show 函数演示很直观,能看出优先级决定绑定关系、短路决定实际执行。末尾权限位函数好像断在 def 了,方便的话可以补齐完整示例,大家排查起来会更直观。整体口诀辅助记忆够用,但遇到复杂表达式,加括号仍然是最省心的做法。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-11-7 05:01 , Processed in 0.023650 second(s), 28 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部