Python运算符优先级表:复杂表达式怎么确定计算顺序
提供完整的Python 3运算符优先级与结合性对照表,详解复杂表达式确定计算顺序的三大核心规则(优先级、结合方向、操作数左到右求值与短路机制),拆解高频易错陷阱并给出AST语法树验证方法。
假设你在排查一段订单风控或优惠计算的Python代码时,遇到这样一行没有括号的表达式:not is_blocked and points // 10 + 5 * 2 ** 2 > 25 or flag & 1 == 1。当计算结果与预期不符时,靠直觉从左往右读往往会找错问题位置。在Python中,确定复杂表达式的计算顺序只需要依次对照三件事:先按运算符优先级表把高优先级运算绑定成子表达式,同级运算符按结合方向(左结合、右结合或链式展开)归组,最后按从左到右的操作数求值顺序与短路规则执行计算。本文将给出完整的Python 3运算符优先级对照表,并配合分步推导、高频易错场景和ast标准库验证方法,帮你准确判断任意复杂表达式的执行顺序。
一、Python 3完整运算符优先级与结合性对照表
在Python 3官方语言参考手册(Python Language Reference)中,所有运算符按照绑定紧密程度分为18个等级。表格中位置越靠前(等级数字越小),优先级越高,越先被绑定计算;同一行内的运算符具有完全相同的优先级。
| 优先级等级 | 运算符 / 表达式形式 | 含义说明 | 结合方向 | 典型示例与等价括号形式 |
|---|---|---|---|---|
| 1(最高) | (expr...)、[expr...]、{k: v...}、{expr...} |
圆括号分组、列表/字典/集合推导式与字面量 | 从左到右 | (a + b) * c 强制先算 a + b |
| 2 | x[index]、x[i:j]、x(args...)、x.attr |
下标索引、切片、函数/方法调用、属性引用 | 从左到右 | obj.items()[0] 等价于 (obj.items())[0] |
| 3 | await x |
协程等待表达式(Python 3.5+) | 单目(前缀) | await client.get() 等价于 await (client.get()) |
| 4 | ** |
幂运算(乘方) | 从右到左 | 2 ** 3 ** 2 等价于 2 ** (3 ** 2) = 512 |
| 5 | +x、-x、~x |
一元正号、一元负号(取反)、按位取反 | 从右到左(单目) | -3 ** 2 等价于 -(3 ** 2) = -9 |
| 6 | *、@、/、//、% |
乘法、矩阵乘法、浮点除法、向下取整除、取模 | 从左到右 | 20 // 3 * 2 等价于 (20 // 3) * 2 = 12 |
| 7 | +、- |
加法、减法 | 从左到右 | 10 - 3 + 2 等价于 (10 - 3) + 2 = 9 |
| 8 | <<、>> |
按位左移、按位右移 | 从左到右 | 1 << 2 + 1 等价于 1 << (2 + 1) = 8 |
| 9 | & |
按位与(集合交集) | 从左到右 | 6 & 3 + 1 等价于 6 & (3 + 1) = 4 |
| 10 | ^ |
按位异或(集合对称差集) | 从左到右 | 5 ^ 3 & 1 等价于 5 ^ (3 & 1) = 4 |
| 11 | | |
按位或(集合并集 / Python 3.9+字典合并) | 从左到右 | 4 | 2 ^ 3 等价于 4 | (2 ^ 3) = 5 |
| 12 | in、not in、is、is not、<、<=、>、>=、!=、== |
成员检测、身份检测、大小与相等比较 | 链式展开 | a < b <= c 等价于 (a < b) and (b <= c) |
| 13 | not x |
布尔逻辑“非” | 从右到左(单目) | not a == b 等价于 not (a == b) |
| 14 | and |
布尔逻辑“与”(短路求值) | 从左到右 | a or b and c 等价于 a or (b and c) |
| 15 | or |
布尔逻辑“或”(短路求值) | 从左到右 | a and b or c 等价于 (a and b) or c |
| 16 | x if c else y |
条件表达式(三元运算符) | 从右到左 | a if c1 else b if c2 else d 等价于 a if c1 else (b if c2 else d) |
| 17 | lambda args: expr |
匿名函数定义 | 无(整体绑定右侧) | lambda x: x + 1 将 x + 1 作为函数体 |
| 18(最低) | := |
赋值表达式(海象运算符,Python 3.8+) | 从右到左 | (n := len(a)) > 5 必须加括号,否则 n 会绑定布尔值 |
& > 异或^ > 或|) > 比较与身份/成员测试 > 逻辑运算(not > and > or) > 三元条件与lambda > 海象赋值:=。
二、确定复杂表达式计算顺序的三大核心规则
拿来一张优先级表,并不等于能直接算出所有复杂表达式的结果。在Python解释器内部,“谁和谁先绑在一起(语法结构)”与“哪个子表达式先执行(运行时求值)”分别由以下三条规则共同决定。
1. 不同等级看优先级:高优先级先聚合成子单元
当一个表达式里同时出现不同等级的运算符时,优先级高的运算符会率先把它左右两侧的操作数“抓取”过来,相当于在它们外层自动套上一对隐式圆括号。例如在 a + b * c ** d 中,**(等级4)高于 *(等级6),* 又高于 +(等级7),因此解释器生成的语法结构必然是 a + (b * (c ** d))。
2. 相同等级看结合性:左结合、右结合与链式比较
当相邻的两个运算符处于同一优先级等级时,谁先绑定取决于该等级的“结合方向”:
- 从左到右结合(左结合,绝大多数二元运算符):加减(
+、-)、乘除取模(*、/、//、%)、位运算、逻辑与(and)、逻辑或(or)均采用左结合。例如20 // 3 * 2中,//和*同属等级6,按从左到右结合为(20 // 3) * 2,先算20 // 3得6,再算6 * 2得12。 - 从右到左结合(右结合,注意幂运算与条件表达式):二元运算符中,幂运算
**、三元条件表达式x if c else y和海象运算符:=采用右结合;所有单目(前缀)运算符如-x、~x、not x自然也是从右向左包裹。例如2 ** 3 ** 2会从右侧先绑定为2 ** (3 ** 2),结果是2 ** 9 = 512,而不是(2 ** 3) ** 2 = 64。 - 链式比较展开(等级12专属机制):所有比较运算符(
<、>、==、!=、in、is等)优先级完全相同。当它们连续出现时,既不是单纯的左结合也不是右结合,而是被展开为用and连接的多个比较,且中间公共表达式只计算一次。例如a < f(x) <= b等价于先计算一次temp = f(x),再判断(a < temp) and (temp <= b)。
3. 操作数求值顺序:从左到右执行与短路跳出
很多人容易把“运算符优先级高”误解为“整行代码里那一部分最先被CPU执行”。实际上,优先级只决定语法树怎么画括号,而在真正执行时,Python总是从左到右依次对各个操作数求值(赋值语句除外,赋值语句先对等号右侧求值)。
比如表达式 f1() + f2() * f3():虽然乘法 * 优先级高于加法 +(结构等价于 f1() + (f2() * f3())),但Python运行时会严格按照从左往右的顺序依次调用 f1()、f2()、f3(),拿到三个返回值后,再先做乘法、后做加法。
唯一会打断“全部从左到右求值”的是短路求值(Short-circuit evaluation):
x and y:先计算左操作数x;若x的布尔值为假(如False、0、""、None、[]),直接返回x的原值,完全不计算y;否则计算并返回y的原值。x or y:先计算左操作数x;若x的布尔值为真,直接返回x的原值,完全不计算y;否则计算并返回y的原值。x if c else y:先计算中间的条件c;若c为真则只计算并返回x,若c为假则只计算并返回y。
三、复杂表达式怎么一步步确定计算顺序(实战拆解)
面对一个包含算术、位运算、比较和布尔逻辑的复杂表达式,可以按照“四步括号法”手工还原它的完整计算顺序。我们以下面这行表达式为例:
result = not 10 + 2 * 3 ** 2 > 30 or 8 & 1 == 1 and 5
第1步:扫描并圈出算术运算(等级4~7)
在表达式左侧的 10 + 2 * 3 ** 2 中,包含 **(等级4)、*(等级6)、+(等级7):
- 先绑定最高优先级的幂运算:
3 ** 2→9。 - 再绑定乘法:
2 * (3 ** 2)→2 * 9 = 18。 - 最后绑定加法:
10 + (2 * (3 ** 2))→10 + 18 = 28。
第2步:扫描并圈出位运算(等级8~11)
在表达式右侧的 8 & 1 == 1 中,许多初学者以为先算 1 == 1,但查表可知按位与 &(等级9)高于相等比较 ==(等级12)。因此必须先绑定 8 & 1:
8的二进制是1000,1的二进制是0001,按位与(8 & 1)结果为0。
第3步:扫描并圈出比较运算(等级12)
算术和位运算归并完毕后,处理比较运算符 > 和 ==:
- 左侧比较:
(10 + 2 * 3 ** 2) > 30即28 > 30,结果为False。 - 右侧比较:
(8 & 1) == 1即0 == 1,结果为False。
第4步:按 not > and > or 归并逻辑运算(等级13~15)
此时原表达式已经简化为:not False or False and 5。按照逻辑运算符内部的严格顺序 not(等级13) > and(等级14) > or(等级15):
- 先绑定
not:(not False)得到True。此时表达式等价于True or (False and 5)。 - 绑定
and:右侧子表达式聚合成(False and 5)。 - 最后计算顶层的
or:由于or左操作数已经是True,触发短路求值,右侧的(False and 5)甚至不需要真正执行,整个表达式直接返回True。
我们可以在Python 3交互式终端或脚本(推荐Python 3.8及以上环境)中验证上述分步结果与完全加括号后的等价性:
# 文件名:verify_order.py(在任意 Python 3 环境下运行)
expr_raw = not 10 + 2 * 3 ** 2 > 30 or 8 & 1 == 1 and 5
expr_grouped = (not ((10 + (2 * (3 ** 2))) > 30)) or (((8 & 1) == 1) and 5)
print("无括号表达式结果:", expr_raw)
print("全括号表达式结果:", expr_grouped)
print("右半部分单独求值:", ((8 & 1) == 1) and 5)
全括号表达式结果: True
右半部分单独求值: False
四、最容易算错的五类优先级陷阱
实际编码中,绝大多数优先级Bug都集中在以下五组看似符合直觉、实则绑定顺序相反的表达式上。
1. 幂运算 ** 与一元负号 -:-3 ** 2 等于 -9 而不是 9
幂运算符 **(等级4)的优先级比左侧的一元正负号 +x、-x(等级5)更高。因此写下 -3 ** 2 时,Python会先算 3 ** 2 得到 9,再取负号得到 -9。但是,如果负号出现在指数位置(即 ** 右侧),例如 2 ** -3,它是唯一合法的结合方式 2 ** (-3) = 0.125。
(-3) ** 2;如果是变量(如 x = -3; x ** 2),因为变量求值直接得到对象 -3,结果才是 9。
2. 位运算符与比较运算符:x == 1 & 3 会先算 1 & 3
在C或Java等部分场景中,开发者常混用按位与 &、按位或 | 和相等比较 ==。在Python以及Pandas/NumPy数组过滤中,位运算符 &(等级9)、^(等级10)、|(等级11)的优先级全部高于比较运算符 ==、>、<(等级12)。
如果写出 a == 1 & b == 2,Python会将其解析为 a == (1 & b) == 2(即链式比较 a == (1 & b) and (1 & b) == 2),而不是预期的两个条件同时成立。在Pandas筛选DataFrame行时,写成 df[df['a'] > 0 & df['b'] > 0] 更会因为先算 0 & df['b'] 而直接抛出 ValueError 或得到错误结果。
3. 链式比较陷阱:1 == 1 in [1] 与 True == True == False
因为 ==、!=、<、>、in、is 全都处于等级12,一旦它们连在一起写,无论你本意是什么,都会触发链式比较展开:
1 == 1 in [1]:不会先算1 == 1得到True再算True in [1];而是被拆解为(1 == 1) and (1 in [1]),两边都为True,最终结果为True。True == True == False:被拆解为(True == True) and (True == False),结果为False;但如果加了括号写成(True == True) == False,左半边算出True,再算True == False,虽然这个例子碰巧也是False,但换成(1 < 2) == True(结果为True)与不加括号的1 < 2 == True(展开为1 < 2 and 2 == True,结果为False),结果就会完全相反!
4. not 与比较运算符的位置关系:not a == b 合法,但 a == not b 报错
逻辑非 not(等级13)的优先级低于所有比较运算符(等级12)。因此:
- 写
not a == b时,会先计算a == b,再对结果取反,等价于not (a == b)。 - 但如果反过来写
a == not b,由于==优先级比not高,解释器在==右侧期待一个比它优先级更高的表达式,结果撞上了低优先级的not,会直接抛出语法错误SyntaxError: invalid syntax。此时必须显式写为a == (not b)。
5. 海象运算符 := 优先级垫底:if x := get_val() > 0 会把布尔值赋给 x
Python 3.8引入的赋值表达式 :=(等级18)优先级低于除逗号外的所有运算符。如果你写:
# 错误写法:本意是把 len(items) 赋给 n,再判断 n > 3
if n := len(items) > 3:
print(n)
由于 >(等级12)远高于 :=(等级18),Python会先计算 len(items) > 3 得到布尔值 True 或 False,然后把布尔值赋给变量 n。正确写法必须给赋值表达式加上括号:if (n := len(items)) > 3:。
五、用Python内置ast模块直接查看真实计算顺序
如果在实际项目中遇到一行极其复杂的遗留表达式,不想靠人工查表冒改错逻辑的风险,最稳妥的方法是直接使用Python标准库自带的 ast(抽象语法树)模块。ast.parse() 会调用Python解释器自身的语法分析器,将表达式的真实嵌套结构原原本本打印出来,无需安装任何第三方依赖。
操作步骤与验证代码
在本地终端或任意Python 3.9及以上环境(Python 3.9为 ast.dump() 新增了 indent 缩进参数,若是Python 3.8及以下去掉 indent=2 即可)运行以下脚本:
# 文件名:inspect_ast.py(Python 3.9+ 标准库直接运行)
import ast
def show_eval_structure(expr_str: str) -> None:
tree = ast.parse(expr_str, mode="eval")
print(f"=== 表达式: {expr_str} ===")
print(ast.dump(tree.body, indent=2))
# 验证 1:负号与幂运算
show_eval_structure("-3 ** 2")
# 验证 2:位运算与相等比较、逻辑与
show_eval_structure("8 & 1 == 1 and 5")
UnaryOp(
op=USub(),
operand=BinOp(
left=Constant(value=3),
op=Pow(),
right=Constant(value=2)))
=== 表达式: 8 & 1 == 1 and 5 ===
BoolOp(
op=And(),
values=[
Compare(
left=BinOp(
left=Constant(value=8),
op=BitAnd(),
right=Constant(value=1)),
ops=[Eq()],
comparators=[Constant(value=1)]),
Constant(value=5)])
如何阅读 ast.dump() 的输出:
- 缩进越深(越往里层)的节点,优先级越高,越先绑定成一个整体:在第一个输出中,最外层是
UnaryOp(op=USub())(一元负号),而它的操作数operand里面包裹着BinOp(op=Pow())(幂运算3 ** 2),一眼就能确认是先算平方再取负。 - 在第二个输出中,最内层是
BinOp(op=BitAnd())(8 & 1),往外一层是Compare(ops=[Eq()])(== 1),最外层才是BoolOp(op=And())(and 5),层级关系一目了然。
六、复杂表达式顺序判定与代码重构总结
掌握运算符优先级表的核心目的,是在阅读他人代码时能准确推导出执行结果,而在自己编写代码时主动消除歧义。当表达式跨越多于两个优先级梯队(例如同时混用算术、位运算与多种布尔逻辑)时,建议遵循以下工程实践:
- 基本的“乘除先于加减、比较先于
and/or”可以不加括号:例如price * count + shipping > 100 and is_vip符合所有开发者的普遍直觉,保留原样更加简洁。 - 凡是涉及位运算(
&、|、^、<<、>>)、幂运算配负号、三元表达式嵌套、and与or混合、海象运算符:=,一律显式加上圆括号():圆括号在Python中属于最高优先级(等级1),不会带来任何额外的运行时性能损耗。 - 超过3层逻辑组合时,拆分为具名布尔变量:与其在一行里堆砌 8 个运算符,不如将子条件提取为语义清晰的中间变量(如
has_quota = ...、is_valid_user = ...),既能固定求值顺序,也方便在调试器中单步查看每个子条件的真假值。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









