跳转到内容
新建笔记

Python 运算符:优先级、短路与对象语义

运算符优先级决定表达式如何分组;求值顺序、短路和对象类型决定实际执行什么。读不熟悉的组合时,加括号通常比背诵规则更清楚。本文以 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 == -4
assert (-2) ** 2 == 4
assert 2 ** -2 == 0.25
assert 2 ** 3 ** 2 == 512
assert (2 ** 3) ** 2 == 64
assert 20 / 3 > 6
assert -7 // 3 == -3 and -7 % 3 == 2
assert 7 // -3 == -3 and 7 % -3 == -2
for a, b in [(-7, 3), (7, -3), (-7, -3), (7, 3)]:
assert a == (a // b) * b + a % b
x, y = 0b1100, 0b1010
assert (x & y, x | y, x ^ y) == (8, 14, 6)
assert ~x == -x - 1
assert x << 1 == 24 and x >> 2 == 3
assert -3 >> 1 == -2
assert 1 << 2 + 1 == 8
assert (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() < 3
assert calls == ["middle"]
assert not (3 < 2 < forbidden())
assert (0 and forbidden()) == 0
assert ("ready" or forbidden()) == "ready"
assert ([] or [1]) == [1]
assert ("ready" and 9) == 9
assert (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 == 3
assert length == 3
print("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 = a
assert a == b and a is not b
assert 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 record
assert "Alice" in record.values()
assert 2 in {1, 2, 3}
iterator = iter([1, 2, 3])
assert 2 in iterator
assert next(iterator) == 3
assert 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:
pass
else:
raise AssertionError("lists do not implement matrix multiplication")
print("matrix multiplication checks passed")

参考:比较、成员与身份、布尔运算、id 的保证、NumPy matmul。