海象运算符
海象运算符 :=(正式名称为"赋值表达式")在 Python 3.8 中引入,它允许在表达式内部完成赋值,将赋值和求值合并为一步。这个运算符因形似海象的獠牙而得名,解决了"先赋值再判断"场景中代码重复或需要额外变量的问题。
基本语法
海象运算符的语法是 (变量 := 表达式)。括号在大部分场景中是必需的,因为 := 的优先级较低。赋值完成后,整个表达式的值就是右侧表达式的值。
>>> (x := 5)
5
>>> x
5
与普通赋值 = 不同,海象运算符可以在不允许语句出现的表达式上下文中使用,比如 if 条件、while 条件、列表推导的过滤条件等。
while 循环场景
海象运算符最经典的用途是简化 while 循环中"读取-判断-处理"的模式。传统写法需要在循环前读取一次,循环体内再次读取,造成代码重复。
# 传统写法:input() 出现两次
line = input("请输入:")
while line != "quit":
print(f"处理:{line}")
line = input("请输入:")
使用海象运算符,可以将读取和判断合并:
# 海象运算符写法
while (line := input("请输入:")) != "quit":
print(f"处理:{line}")
括号在这里是必需的,因为 := 的优先级低于 !=。如果不加括号,line := input("请输入:") != "quit" 会被解析为 line := (input(...) != "quit"),将布尔值赋给 line。
文件读取是另一个典型场景:
# 传统写法
with open("data.txt") as f:
line = f.readline()
while line:
print(line.strip())
line = f.readline()
# 海象运算符写法
with open("data.txt") as f:
while (line := f.readline()):
print(line.strip())
注意这里利用了一个细节:空字符串 "" 在布尔上下文中为 False,当文件读到末尾时 readline() 返回 "",循环自然结束。
if 场景
在 if 语句中,海象运算符可以在判断条件的同时保存中间结果,供 if 块和可能的 else 块复用。
# 传统写法:计算两次
if len(data) > 10:
print(f"数据很长,长度是 {len(data)}")
# 海象运算符写法:计算一次
if (n := len(data)) > 10:
print(f"数据很长,长度是 {n}")
正则表达式匹配是 if 场景的高频用例:
import re
# 传统写法
match = re.search(r'(\d+)', text)
if match:
print(f"找到数字:{match.group(1)}")
# 海象运算符写法
if (match := re.search(r'(\d+)', text)):
print(f"找到数字:{match.group(1)}")
在 elif 链中,海象运算符可以依次尝试不同的模式:
# 依次尝试不同的正则匹配
if (m := re.search(r'ERROR: (.+)', line)):
print(f"错误:{m.group(1)}")
elif (m := re.search(r'WARN: (.+)', line)):
print(f"警告:{m.group(1)}")
elif (m := re.search(r'INFO: (.+)', line)):
print(f"信息:{m.group(1)}")
列表推导场景
在列表推导(以及字典推导、集合推导、生成器表达式)中,海象运算符可以避免重复计算昂贵的表达式。
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 传统写法:f(x) 被计算两次
results = [y for x in data if (y := x ** 2) > 20]
# 等价于:
# results = []
# for x in data:
# y = x ** 2
# if y > 20:
# results.append(y)
>>> results
[25, 36, 49, 64, 81, 100]
更实用的例子是过滤并转换数据时复用计算结果:
# 获取所有文件的修改时间,只保留最近修改的
import os
files = [f for f in os.listdir('.') if (t := os.path.getmtime(f)) > threshold]
# 这里 t 被赋值但未被后续使用,更好的写法可能不需要海象运算符
一个更典型的列表推导场景是:需要同时过滤和输出基于同一计算的多个值。
# 计算平方根,只保留整数平方根
import math
numbers = [1, 2, 3, 4, 5, 9, 10, 16, 20]
perfect_squares = [n for n in numbers if (root := math.isqrt(n)) ** 2 == n]
>>> perfect_squares
[1, 4, 9, 16]
注意事项
作用域规则
海象运算符在 Python 3.8 最初引入时,如果在列表推导中使用,赋值的变量会"泄漏"到外部作用域。Python 3.12 已修复此行为,列表推导中的海象赋值现在正确限制在推导内部。
# Python 3.8-3.11(变量泄漏到外部)
>>> [y := x + 1 for x in range(3)]
[1, 2, 3]
>>> y # y 泄漏了!
3
# Python 3.12+(变量限制在推导内)
>>> [y := x + 1 for x in range(3)]
[1, 2, 3]
>>> y
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
可读性优先
海象运算符的目的是消除代码重复,而非压缩代码行数。如果普通赋值更清晰,就不应强行使用海象运算符。
# ❌ 不要为了用而用
if (x := calculate()):
process(x)
# ✅ 简单场景直接用普通赋值
x = calculate()
if x:
process(x)
括号要求
海象运算符在大多数上下文中需要括号,因为它是最低优先级的运算符之一。常见的错误是遗漏括号导致语法或逻辑错误。
# 错误:优先级问题
>>> if x := 5 > 3:
... print(x)
...
True # x 被赋值为 (5 > 3) 即 True,不是 5
# 正确
>>> if (x := 5) > 3:
... print(x)
...
5
不能替代所有赋值
海象运算符不能用于纯赋值语句场景——它必须作为表达式的一部分。以下写法是语法错误:
>>> (x := 5); (y := 10) # 可以,作为表达式语句
>>> x := 5 # 语法错误,不能作为独立语句
File "<stdin>", line 1
x := 5
^
SyntaxError: invalid syntax
海象运算符是 Python 语法演进中实用主义的体现。它在保持代码可读性的前提下,优雅地解决了"赋值+判断"模式中的重复问题。掌握其适用场景和边界,能让循环和条件代码更加紧凑而不晦涩。