运算符优先级决定表达式如何分组;求值顺序、短路和对象类型决定实际执行什么。读不熟悉的组合时,加括号通常比背诵规则更清楚。本文以 Python 3.11 为基线。
优先级:从高到低
跳转到“优先级:从高到低”同一行通常从左向右结合;幂运算和条件表达式从右向左结合。比较运算还有链式规则,不能按普通二元运算逐个得到布尔值后再比较。原表中被 Markdown 分隔符拆坏的按位或,下面恢复为 |。
| 表达式或运算符 | 用途与边界 |
|---|---|
(表达式)、列表/字典/集合显示形式 | 括号分组及容器构造;非空元组主要由逗号形成 |
x[index]、x[start:stop]、x(args)、x.attribute | 下标、切片、调用、属性访问 |
await x | 等待可等待对象,出现在合法的异步语法环境 |
** | 幂;与一元正负号有下述特殊关系 |
+x、-x、~x | 一元正号、负号、按位取反 |
*、@、/、//、% | 乘、矩阵乘、除、向下取整除、取余;含义依类型而定 |
+、- | 加、减;某些类型另定义拼接等操作 |
<<、>> | 左移、右移 |
& | 按位与;集合类型可表示交集 |
^ | 按位异或;集合类型可表示对称差 |
| | 按位或;集合/字典也定义自己的操作 |
in、not in、is、is not、<、<=、>、>=、!=、== | 成员、身份、比较,同一优先级并支持比较链 |
not x | 逻辑非,结果是布尔值 |
and | 短路逻辑与,返回某个操作数 |
or | 短路逻辑或,返回某个操作数 |
x if condition else y | 条件表达式,只求值被选中的分支 |
lambda | 匿名函数表达式 |
:= | 赋值表达式;在多种位置需要括号 |
普通赋值 = 是语句的一部分,不是把它加入这个表就能参与任意嵌套运算。+= 等增强赋值也有自己的语句规则。完整定义见 Python 表达式优先级。
算术与位运算的易错组合
跳转到“算术与位运算的易错组合”-2 ** 2 表示 -(2 ** 2),而 2 ** -2 允许负指数。2 ** 3 ** 2 表示 2 ** (3 ** 2)。整数的 // 向负无穷方向取整,不能把它当成向零截断;非零除数下,整数满足 a == (a // b) * b + a % b。
assert -2 ** 2 == -4assert (-2) ** 2 == 4assert 2 ** -2 == 0.25assert 2 ** 3 ** 2 == 512assert (2 ** 3) ** 2 == 64assert 20 / 3 > 6assert -7 // 3 == -3 and -7 % 3 == 2assert 7 // -3 == -3 and 7 % -3 == -2for a, b in [(-7, 3), (7, -3), (-7, -3), (7, 3)]: assert a == (a // b) * b + a % b
x, y = 0b1100, 0b1010assert (x & y, x | y, x ^ y) == (8, 14, 6)assert ~x == -x - 1assert x << 1 == 24 and x >> 2 == 3assert -3 >> 1 == -2assert 1 << 2 + 1 == 8assert (x & y) == 8 and (x & y == 8)print("arithmetic and bitwise checks passed")Python 整数位运算按带符号的语义工作,不应直接套用某个固定宽度无符号寄存器的溢出规则。负数移位位数会报错,除数为零也会报错。% 用于字符串时是格式化,用于整数时才是这里的取余。
与 C 比较时,Python 的 not 低于比较、相等与大小比较同级、位运算高于比较,均容易造成移植错误。例如这里的 x & y == 8 按 (x & y) == 8 解释。跨语言代码应直接写清括号,不凭视觉外形推断。
比较链、短路与条件表达式
跳转到“比较链、短路与条件表达式”a < b < c 会在需要时比较两次,但中间的 b 表达式只求值一次;它不是 (a < b) < c。and、or 根据真值选择操作数并返回该对象,只有 not 固定返回布尔值。
calls = []
def middle(): calls.append("middle") return 2
def forbidden(): raise AssertionError("this branch must not execute")
assert 1 < middle() < 3assert calls == ["middle"]assert not (3 < 2 < forbidden())assert (0 and forbidden()) == 0assert ("ready" or forbidden()) == "ready"assert ([] or [1]) == [1]assert ("ready" and 9) == 9assert (not []) is True
chosen = "yes" if True else forbidden()assert chosen == "yes"assert ("a" if False else "b" if True else "c") == "b"choose = lambda value: "positive" if value > 0 else "other"assert choose(2) == "positive" and choose(0) == "other"values = [1, 2, 3]if (length := len(values)) > 2: assert length == 3assert length == 3print("grouping and short-circuit checks passed")or 会把 0、空容器等假值也当作需要回退的情况。若只有 None 表示缺失,应显式判断 is None,不要把所有合法的假值一起替换。赋值表达式中的括号还避免把比较结果错误赋给变量,例如应区分 (length := len(values)) > 2 与 length := (len(values) > 2)。
成员关系、相等与身份
跳转到“成员关系、相等与身份”| 判断 | 询问的问题 | 常见边界 |
|---|---|---|
x in container、x not in container | 是否包含对应成员 | 字符串查子串,字典默认查键;也可用于集合和其他支持成员检测的对象 |
x == y、x != y | 按类型定义,值是否相等 | 两个不同列表可以相等;自定义类型可实现自己的比较 |
x is y、x is not y | 是否为同一个对象 | 不可被自定义比较方法改写;常用于 None 等单例判断 |
id() 提供对象存活期间稳定且唯一的身份整数;“必定是内存地址”不是 Python 语言对所有实现的保证。对象销毁后,这个整数也可能被后来对象复用。不要用小整数或字符串的缓存现象判断两个值应该使用 is 还是 ==。
a = [1, 2]b = [1, 2]alias = aassert a == b and a is not bassert alias is a and id(alias) == id(a)assert id(a) != id(b)assert "ell" in "hello" and "z" not in "hello"record = {"name": "Alice"}assert "name" in record and "Alice" not in recordassert "Alice" in record.values()assert 2 in {1, 2, 3}
iterator = iter([1, 2, 3])assert 2 in iteratorassert next(iterator) == 3assert list(iterator) == []print("membership and identity checks passed")对迭代器做成员检测可能消耗它;若迭代器永不结束且目标从不出现,检测也可能永不返回。身份和相等的进一步讨论见变量与类型。
await 与 @ 需要合适的对象
跳转到“await 与 @ 需要合适的对象”下面仅用标准库展示合法的 await 位置,没有网络请求或后台任务:
import asyncio
async def value(): return 3
async def main(): assert await value() ** 2 == 9 assert (await value()) + 2 == 5
asyncio.run(main())print("await precedence checks passed")@ 是矩阵乘法运算符,内置列表不会自动获得矩阵乘法。下面依赖 NumPy,与逐元素乘法 * 对照;数组维数变化时应再检查 NumPy 的形状规则。
import numpy as np
a = np.array([[1, 2], [3, 4]])b = np.array([[5, 6], [7, 8]])assert np.array_equal(a @ b, [[19, 22], [43, 50]])assert np.array_equal(a * b, [[5, 12], [21, 32]])try: [[1, 2]] @ [[3], [4]]except TypeError: passelse: raise AssertionError("lists do not implement matrix multiplication")print("matrix multiplication checks passed")