只在需同时计算、复用且作用域受限时用 :=;它非语法糖,旨在避免重复调用或污染作用域,误用会导致函数多次执行或逻辑难读。

什么时候该用 :=,而不是先赋值再判断?
只在「需要同时计算、复用、且作用域受限」的场景下用 :=。它不是语法糖,而是为避免重复调用或临时变量污染作用域而生的工具。
常见错误是把 if x := expensive_func(): 当成“更酷的写法”来用,结果函数被调了两次(比如误写成 if expensive_func() and x := expensive_func():),或者把本该提前初始化的变量硬塞进条件里,导致逻辑难读。
- 适用:正则匹配后立刻取
.group()、文件读取一行并检查非空、列表推导中过滤+转换共用中间结果 - 不适用:赋值本身有副作用(如修改全局状态)、右侧表达式含可变对象且后续还要用原值、嵌套过深(比如
if (a := f()) and (b := g(a)) and (c := h(b)):) - 性能影响:无额外开销,但若右侧是耗时操作,
:=不会帮你缓存——它只保证一次求值,前提是写对了位置
:= 在列表推导和生成器表达式里的坑
这是最容易翻车的地方::= 在推导式里只能出现在条件部分(if 或 for 子句),不能放在表达式主体开头,否则报 SyntaxError: invalid syntax。
比如你想从字符串列表中提取长度大于 3 的首字母,别写 [x[0] for x in words if (n := len(x)) > 3] —— 这合法;但别写 [(n := len(x)), x[0] for x in words if n > 3],因为 n 在 if 外不可见,会报 NameError: name 'n' is not defined。
立即学习“Python免费学习笔记(深入)”;
- 正确模式:
[f(x) for x in data if (tmp := helper(x)) and tmp.is_valid] - 错误模式:
[tmp for x in data if (tmp := helper(x))](tmp在列表项里用不到,纯属干扰) - 兼容性注意:python
调试时 := 让 print 变得又快又脏
快速验证某个表达式的值,又不想拆成两行,print(x := some_expr) 是最直接的办法——它既输出结果,又把值存下来供后续用。
但要注意:这会改变原表达式的执行时机。比如 print(y := input("Enter: ")) 每次都会触发输入,如果后面还用 y,就得确保没重复调用;再比如 print(z := risky_func()) or z 看似巧妙,实则掩盖了 risky_func() 可能抛异常的问题。
- 安全用法:
assert (val := compute()) == expected, f"Got {val}" - 危险用法:
if (res := slow_db_query()) and res.get("ok"): ...—— 若slow_db_query()超时或失败,整个条件崩,但你可能误以为是逻辑问题而非 IO 问题 - ide 支持差:多数调试器不把
:=绑定的变量自动加入 locals 面板,得手动敲变量名查
和传统赋值比,:= 对作用域的隐含约束
:= 不创建新作用域,但它受所在语句的作用域规则严格约束。最典型的是在 while 条件中使用,变量会在循环外依然存在——这点和 C/js 的 let 完全不同。
比如 while (line := fp.readline()): process(line) 结束后,line 仍绑定到最后读到的内容(包括空字符串),不是未定义。这个行为常被忽略,导致后续代码误用残留值。
- 函数内用
:=,变量属于当前函数作用域,不会泄漏到模块级 - 类定义体里不能用
:=赋值(会报SyntaxError),因为类体不是表达式上下文 - Lambda 里可用,但可读性极差,例如
lambda x: (y := x * 2) + y—— 别这么干
真正麻烦的从来不是语法会不会,而是你忘了那个变量还在那儿,而且刚被悄悄改过。