← 返回题目列表

Python 的海象运算符 := 是什么?和普通赋值有什么区别?

简单 第 16 / 27 题 更新于 2026/07/31
海象运算符赋值表达式PEP 572Python 3.8

简化版

海象运算符 :=(正式名「赋值表达式」,Python 3.8+ 引入,PEP 572)的作用是「在一个表达式内部顺手把值绑定到变量」——它既完成赋值,本身又有值,所以能写在 ifwhile、推导式这些「只能放表达式的地方」。普通赋值 x = 1语句(statement),没有值、不能嵌进别的表达式;x := 1表达式(expression),有值,可以嵌。三个经典用途:while 读取循环——while (chunk := f.read(8192)): 一行搞定「读一次、判断、再读」的重复;if 里复用匹配结果——if (m := re.match(p, s)): 直接用 m.group(),不用先赋值再判断;③ 推导式里避免重复计算——[y for x in data if (y := f(x)) > 0]f(x) 只算一次而不是算两次。注意:为了防止和 == 混淆,语法上做了限制——顶层语句里不能单独写 x := 1(必须加括号或本身在表达式里)、不能给属性或下标赋值obj.a := 1d['k'] := 1 都非法);另外它在推导式里绑定的变量会泄漏到外层作用域(这是有意设计)。名字来源::= 侧过来像海象的眼睛和獠牙。核心记忆::= 让「赋值」变成有值的表达式,主要用来消除「先算一遍再判断、判断完再算一遍」的重复。

详细版

=:= 的区别

维度=(赋值语句):=(赋值表达式)
本质语句,没有值表达式,值就是被赋的值
能否嵌入不能(if x = f(): 是语法错误)能(if (x := f()):
支持目标变量、属性、下标、解包只能是简单变量名
增强赋值x += 1 可以x :+= 1 不存在
链式a = b = 1 可以不支持链式
版本一直有3.8+
import re

# ① while 读取:消除"读两次"的重复
with open("big.log", "rb") as f:
    while chunk := f.read(8192):          # 读 + 判断一步到位
        process(chunk)
# 旧写法要写两遍 f.read:
#   chunk = f.read(8192)
#   while chunk:
#       process(chunk)
#       chunk = f.read(8192)              # ← 忘了这行就是死循环

# ② if 里复用匹配结果
line = "score=95"
if m := re.match(r"score=(\d+)", line):   # 匹配 + 判断 + 保留结果
    print(int(m.group(1)))                # 95
# 旧写法:m = re.match(...) 然后 if m:(两行)

# ③ 推导式里避免重复计算(f 只调用一次)
data = [1, -2, 3]
def f(x): return x * 10
print([y for x in data if (y := f(x)) > 0])     # [10, 30]
# 不用 := 就得写 [f(x) for x in data if f(x) > 0] ← f 被调用两次

# ④ 顺手记录中间值(比如求和的同时记住最后一项)
values = [1, 2, 3]
total = sum(last := v for v in values)
print(total, last)                        # 6 3   ← last 泄漏到了外层(有意设计)

# ⑤ 语法限制
# x := 1              # ✗ SyntaxError:顶层不能裸写
(x := 1)              # ✓ 加括号就行
# obj.attr := 1       # ✗ 只能绑定简单变量名
# d['k'] := 1         # ✗ 同上

# ⑥ 优先级坑::= 的优先级最低,几乎总要加括号
n = 10
# if n := n + 1 > 10:   ← 实际是 n := (n + 1 > 10),n 变成了 True!
if (n := n + 1) > 10:   # ✓ 括号明确
    print(n)            # 11

⚠️ 两个必须记住的点:① := 的优先级极低,低于几乎所有运算符,所以 n := n + 1 > 10 会被解析成 n := ((n + 1) > 10)——变量 n 被赋成了布尔值 True,而不是 11,这类 bug 隐蔽且难查。实践准则是只要 := 出现在更大的表达式里,就把它整个用括号包起来(n := n + 1) > 10。② 推导式里的 := 绑定会「泄漏」到外层作用域,这和推导式的循环变量([x for x in ...] 里的 x 用完即消失)行为相反,是 PEP 572 有意为之的设计——目的正是让你能在推导式外面拿到「最后一次计算的中间值」(如上面的 last)。它遵循的规则是::= 绑定到包含推导式的那个作用域(在函数里就是函数的局部变量,在模块层就是全局变量)。有一个例外:在类体内的推导式里用 := 会抛 SyntaxError;另外也不能用 := 去绑定推导式自己的迭代变量([(x := y) for x in data] 里若目标名与循环变量同名会报错)。

完整版教学

一、为什么需要它:三种「重复」的代码

Python 3.8 之前,"想在判断的同时保住结果"只能靠重复。三种典型重复:

重复类型 ①:调用两次(浪费性能,且函数有副作用时行为不同)
  ✗ [f(x) for x in data if f(x) > 0]        f 被调用 2n 次
  ✓ [y for x in data if (y := f(x)) > 0]    f 只调用 n 次
  算例:f 是一次 30ms 的网络请求、data 有 100 项
       旧写法 200 次 × 30ms = 6.0 秒
       新写法 100 次 × 30ms = 3.0 秒        ← 直接省一半

重复类型 ②:写两遍读取(容易漏掉一处 → 死循环)
  ✗ chunk = f.read(n)
     while chunk:
         process(chunk)
         chunk = f.read(n)      ← 这行漏了就死循环,是新手经典 bug
  ✓ while chunk := f.read(n):
         process(chunk)

重复类型 ③:多写一行临时变量(把判断和赋值拆成两句)
  ✗ m = re.match(p, s)
     if m:
         use(m)
  ✓ if m := re.match(p, s):
         use(m)

三种重复的共同点:
  "我需要一个值来做判断,判断完还要用这个值"
  → 而 if / while / 推导式的条件位置只接受"表达式",赋值语句进不去
  → := 就是为了让"赋值"具备表达式身份而生的

海象运算符解决的是一个很具体的语法缺口:Python 里 ifwhile、推导式的条件位置只能放表达式,而普通赋值 x = f() 是语句,于是「拿到值 → 判断 → 还要用这个值」这件极常见的事只能靠重复来表达——要么把函数调两遍(性能翻倍,函数有副作用时甚至语义都变了),要么把读取写两遍(漏一处就死循环),要么多加一行临时变量(把本来一体的逻辑拆成两句)。上面算例里,f 是 30ms 的网络请求、data 有 100 项时,旧写法要多花整整 3 秒。:= 补上的就是「让赋值具备表达式身份」这块拼图,它不提供任何新能力,只消除重复。

二、语法与限制:为什么处处受限

基本形式:  名字 := 表达式        (整体的值 = 被赋的值)

限制一:顶层语句里不能裸写
  x := 1        ✗ SyntaxError
  (x := 1)      ✓ 加括号合法(但没人这么写,直接 x = 1 就行)
  原因:防止把 == 误写成 := 之后代码"还能跑"
       (C 语言里 if (x = 1) 这种笔误制造了几十年的 bug)

限制二:目标只能是简单变量名
  x := 1           ✓
  obj.attr := 1    ✗ SyntaxError
  d['k'] := 1      ✗ SyntaxError
  a, b := 1, 2     ✗(不支持解包)
  原因:保持"赋值表达式"足够简单、可读;复杂目标请用普通赋值

限制三:没有增强形式
  x += 1  ✓(语句)      x :+= 1  ✗(不存在这种写法)

限制四:优先级最低(★最容易踩★)
  n := n + 1 > 10   实际解析为   n := ((n + 1) > 10)
  → n 被赋成 True/False,而不是数字!
  ✓ 铁律:出现在更大表达式里就整体加括号  (n := n + 1) > 10

限制五:不能绑定推导式自己的循环变量
  [(x := 1) for x in data]     ✗ 目标名与迭代变量冲突 → SyntaxError

限制六:类体内的推导式里不能用
  class C:
      vals = [y for x in range(3) if (y := x)]    ✗ SyntaxError
  原因:类作用域和推导式作用域的交互本身就有历史包袱

限制七:函数调用的关键字参数位置要小心
  f(x = 1)       传关键字参数 x
  f(x := 1)      ★传位置参数 1,顺便把 1 绑给变量 x★  ← 含义完全不同

这一串限制不是随意加的,核心动机是防止 := 变成 = 的笔误温床。C 系语言里 if (x = 1) 这种把 == 写成 = 的笔误合法可运行,制造了几十年的隐蔽 bug;Python 的对策是:普通赋值仍然只能是语句(if x = 1: 直接语法错误),而 := 又被限制得只能用在「确实需要表达式赋值」的地方——顶层裸写非法、不能给属性和下标赋值、没有 :+=。最需要肌肉记忆的是限制四(优先级最低)n := n + 1 > 10 会把布尔值赋给 n,值和类型全错却不报错。还有一个冷门但致命的坑是限制七f(x = 1) 是传关键字参数,f(x := 1) 是传位置参数并顺手把 1 赋给局部变量 x——一个字符之差,语义完全不同。

三、作用域:为什么推导式里的绑定会「泄漏」

先回忆推导式的作用域规则(Python 3 起):
  [x for x in range(3)]
  print(x)            # ✗ NameError —— 循环变量 x 用完即消失
  原因:推导式有自己独立的作用域(相当于一个隐式的函数)

但 := 的绑定不遵守这条:
  [y := x*2 for x in range(3)]
  print(y)            # ✓ 4  —— 泄漏到了外层!

规则(PEP 572 明确规定):
  := 绑定到"包含这个推导式的作用域",而不是推导式自己的作用域
  在函数里 → 成为该函数的局部变量
  在模块层 → 成为全局变量
  在类体的推导式里 → ★直接 SyntaxError★(禁止)

这是有意设计,为了支持这类写法:
  # 想知道"第一个满足条件的元素",同时又要判断存在性
  if any((found := item).ok for item in items):
      use(found)                      # ← 用到了泄漏出来的 found

  # 求和的同时记住最后一项
  total = sum(last := v for v in data)
  print(total, last)

  # 边过滤边记录命中数
  if (n := len(matches := [x for x in data if ok(x)])) > 0:
      print(f"命中 {n} 条:{matches}")

★ 生成器表达式里要小心"惰性":
  gen = (y := x for x in range(3))    # 还没执行
  # 此时访问 y → NameError(因为一次都还没跑)
  list(gen)                            # 跑完之后 y 才存在(值为 2)
  → 泄漏出来的变量"什么时候有值"取决于迭代进行到哪一步

★ 想显式声明作用域:
  在函数里对全局变量用 := 前,仍然需要 global/nonlocal 声明
  def f():
      global g
      [g := x for x in range(3)]      # 有 global 声明才写全局

推导式的循环变量用完即消失(这是 Python 3 修掉的历史坑),但 := 的绑定反而是有意「泄漏」的——它绑定到包含推导式的那个作用域。初看矛盾,理解意图就顺了:循环变量是推导式的内部实现细节,泄漏出去只会污染命名空间;而 := 是你主动写下的、明确想留住的中间值,泄漏正是它的目的(否则「求和的同时记住最后一项」这类写法根本没法表达)。使用时要注意两点:一是生成器表达式的惰性——变量要等迭代真正跑到那一步才有值,没迭代就访问会 NameError;二是类体内的推导式禁用 :=(直接 SyntaxError),因为类作用域和推导式作用域的交互本身就有历史包袱,PEP 干脆一刀切禁掉。

四、五个真正值得用的场景

场景 1:分块读取 / 流式消费(最经典)
  while chunk := f.read(8192):        文件
  while line := input():              交互输入
  while batch := cur.fetchmany(1000): 数据库游标
  while (msg := q.get()) is not None: 队列哨兵    ← 注意用 is not None,不是真值判断

场景 2:正则匹配后立刻使用
  if m := PATTERN.search(text):
      name, age = m.group("name"), m.group("age")
  elif m := FALLBACK.search(text):    ← elif 里同样能用,链式匹配特别顺
      ...

场景 3:推导式里复用昂贵的中间结果
  results = [r for item in items if (r := expensive(item)) is not None]
  → expensive 只调一次,且过滤和取值用的是同一个结果

场景 4:避免重复的字典/属性查找
  if (user := cache.get(uid)) is not None:
      return user
  → 旧写法要么查两次(if uid in cache: return cache[uid]),要么多一行

场景 5:把复杂条件拆成有名字的片段(可读性反而变好)
  if (total := price * qty) > 1000 and (disc := rate(total)) > 0:
      print(f"金额 {total},折扣 {disc}")
  → total、disc 都在条件里算出来,下面直接用

★ 不该用的地方(会变难读):
  ✗ 一行里塞多个 := :  if (a := f()) and (b := g(a)) and (c := h(b)): ...
  ✗ 只为了省一行而用:  普通两行赋值 + 判断本来就很清晰
  ✗ 在 lambda 里搞副作用:  sorted(data, key=lambda x: (t := x.a) * t)  ← 炫技
  准则:":= 是为了消除重复,不是为了压行"

值得用 := 的场景有一个共同特征:同一个值既要用于判断、又要用于后续处理,而不用它就必须重复。「分块读取」是最经典的(旧写法要把 f.read() 写两遍,漏一处就死循环);「正则匹配后立刻用」次之(elif m := ... 这种链式匹配用旧写法会变成难看的嵌套);「推导式里复用昂贵中间结果」收益最实在(直接把调用次数减半);「避免重复的字典查找」把 if k in d: return d[k] 的两次哈希查找降为一次。反过来,为了压行而用就是滥用——一行里塞好几个 :=、在 lambda 里制造副作用,都会让代码更难读。判断标准很朴素:用了之后重复消失了就是好用法,只是行数变少了就是坏用法

五、和其他写法的对比与取舍

同一个需求(取字典里的值,没有就返回默认)的几种写法:

  ① if uid in cache: return cache[uid]      两次哈希查找
  ② v = cache.get(uid)                       两行
     if v is not None: return v
  ③ if (v := cache.get(uid)) is not None:    一行、一次查找  ← := 版
         return v
  ④ return cache.get(uid, default)           ★最简单的时候根本不需要 :=★

  → 教训:先看有没有更直接的 API,别一有 3.8 就到处塞 :=

同一个需求(过滤 + 转换)的对比:

  ① [f(x) for x in xs if f(x) > 0]           f 调用 2n 次
  ② [y for y in map(f, xs) if y > 0]         ✓ 也只调 n 次,且没有泄漏
  ③ [y for x in xs if (y := f(x)) > 0]       ✓ n 次,但 y 会泄漏到外层
  ④ [y for y in (f(x) for x in xs) if y > 0] ✓ n 次,嵌套生成器,稍绕

  → ② 和 ③ 都对;一层转换用 map 更干净,多个中间变量时 := 更清晰

版本兼容性:
  := 需要 Python 3.8+。要兼容 3.7 及以下(还有大量遗留服务)就不能用;
  语法错误发生在★编译期★,意味着"只要文件里有 := ,整个模块在 3.7 上都 import 不了",
  不是运行到那一行才报错 → 库作者尤其要注意 setup.py 里的 python_requires

风格与团队约定:
  PEP 572 当年争议极大(Guido 因此卸任 BDFL)
  争议点:可读性(是消除重复,还是鼓励把太多东西塞进一行?)
  实践共识:用在上面那 5 个典型场景,其余场合优先普通赋值

:= 是个收益明确但适用面窄的特性,选型时要先问「有没有更直接的写法」:想取默认值就用 dict.get(k, default),想做一层转换过滤 map 同样能避免重复调用,这些场合根本轮不到 :=。版本兼容上有个容易忽略的细节::= 的语法错误发生在编译期,也就是说只要文件里出现了一个 :=,整个模块在 Python 3.7 上连 import 都会失败(而不是运行到那一行才报错),写库时要在 python_requires 里如实声明。最后,PEP 572 当年争议大到直接导致 Guido 卸任 BDFL,争论焦点正是「消除重复」和「鼓励压行」之间的界线——所以团队里用它时保持克制,收敛到少数几个公认清晰的模式即可。

六、常见写法自查清单

写下每个 := 之后,逐条核对:

□ 有没有加够括号?
   if (n := f()) > 10:        ✓
   if n := f() > 10:          ✗ n 变成了布尔值

□ 判空用的是真值还是 is not None?
   while chunk := f.read(n):          ✓ 空 bytes 结束,正确
   while (v := q.get()) is not None:  ✓ 队列可能合法地产出 0/""/[]
   while v := q.get():                ✗ 收到 0 或 "" 会误判为结束

□ 这个 := 消除了重复吗?还是只是压行?
   消除重复 → 留下;只是压行 → 改回两行

□ 泄漏出来的变量名会不会覆盖外层的同名变量?
   total = 100
   xs = [total := x for x in data]    ✗ 外层的 total 被悄悄改掉了

□ 是不是在类体的推导式里?(禁用,SyntaxError)

□ 目标是简单变量名吗?(属性/下标/解包都不行)

□ 目标 Python 版本 ≥ 3.8 吗?(编译期报错,整个模块都导入不了)

□ 一行里是不是塞了超过一个 := ?(超过一个基本都该拆开)

把上面这份清单当成写完 := 之后的 code review 自查表。其中最值得反复强调的两条是括号is not None:前者对应「优先级最低」的语言陷阱,后者对应一类真实事故——用 while v := q.get(): 消费队列时,如果队列里合法地出现了 0""[] 这些假值,循环会提前结束、数据被静默丢弃;正确写法是显式和哨兵值比较 is not None。第三条「消除了重复吗」是判断该不该用 := 的总闸门:用它是为了让同一个计算只写一次,不是为了让代码少一行

记忆钩子:「海象运算符 :=(3.8+,PEP 572)= 让赋值变成『有值的表达式』,从而能写进 if / while / 推导式的条件位置。它不提供新能力,只消除三种重复:①函数调两遍([f(x) for x in xs if f(x)>0][y for x in xs if (y:=f(x))>0],调用次数直接减半)②读取写两遍(while chunk := f.read(8192):,旧写法漏掉循环末尾那行就死循环)③多一行临时变量(if m := re.match(p,s):)。★三条铁律:一是「优先级最低,出现在更大表达式里就整体加括号」(n := n+1 > 10 会把 True 赋给 n);二是「判空优先 is not None」(队列里合法的 0/”/[] 会让真值判断提前结束循环);三是「目标只能是简单变量名」(属性、下标、解包全不行,顶层也不能裸写)。作用域上它反常识地泄漏到包含推导式的外层作用域(这是有意设计,为了留住中间值),但★类体内的推导式里用它直接 SyntaxError★。判断该不该用只看一句:消除了重复就留,只是压行就改回两行。」

七、常见误区与追问

  • 误区:x := 1x = 1 只是写法不同,可以互换。 本质不同:x = 1语句,没有值,不能出现在任何需要表达式的位置(if x = 1: 是语法错误);x := 1表达式,整体的值就是被赋的值,所以能嵌进 if/while/推导式的条件里。而且 := 的能力比 = 弱得多:不能给属性赋值(obj.a := 1 ✗)、不能给下标赋值(d['k'] := 1 ✗)、不能解包(a, b := 1, 2 ✗)、没有增强形式(x :+= 1 不存在)、不能链式(a = b = 1:= 里没有对应写法)。普通赋值能用的地方就用 =,只有在「必须是表达式」的位置才用 :=
  • 误区:if n := n + 1 > 10: 会把 n+1 赋给 n 再比较。 实际解析是 n := ((n + 1) > 10)——:= 的优先级低于几乎所有运算符,所以 n 被赋成了布尔值 True/False,而不是数字。这类 bug 特别阴险:程序照常运行,但变量的类型和值全错了,往往要到很远的地方才暴露(比如后面拿 n 去做算术得到 True + 1 == 2)。铁律是只要 := 出现在更大的表达式里,就把它整体用括号包住(n := n + 1) > 10。很多 linter(如 ruff、pylint)也会对没加括号的赋值表达式告警。
  • 误区:while v := q.get(): 是消费队列的标准写法。 这是真值判断,队列里如果合法地出现 0""[]0.0 这些假值,循环会提前结束,剩下的数据被静默丢弃,且不报任何错。凡是「用哨兵值表示结束」的场景,都必须显式比较:while (v := q.get()) is not None:。同类问题在读文件时不存在(f.read() 读完返回空 bytes/空 str,恰好只有结束时才是假值,所以 while chunk := f.read(n): 是安全的)——区别在于假值是不是只可能出现在结束时,判断前先想清楚这一点。
  • 误区:推导式里的 := 绑定和循环变量一样,用完就消失。 正好相反:循环变量([x for x in ...] 里的 x)作用域限于推导式内部,用完即消失;而 := 绑定的变量会「泄漏」到包含推导式的那个作用域(函数里就是函数局部变量,模块层就是全局变量),这是 PEP 572 有意为之,目的是让你能在推导式外面拿到中间值。带来的风险是静默覆盖外层同名变量:外层已有 total = 100,推导式里写 [total := x for x in data] 会把它悄悄改掉。所以 := 在推导式里要用独特的变量名。另外有两个例外要记住:类体内的推导式里用 := 直接 SyntaxError;生成器表达式是惰性的,泄漏出来的变量要等真正迭代到那一步才有值,没迭代就访问会 NameError
  • 误区:有了 := 就应该到处用,代码更简洁。 := 的价值判据只有一条——它消除了重复吗。消除了「同一个函数被调两遍」「同一段读取被写两遍」就是好用法;只是把两行压成一行、或者一行里塞好几个 :=,就是在牺牲可读性换行数。更常见的错误是忽略了更直接的 API:想取默认值直接 cache.get(uid, default)if (v := cache.get(uid)) is not None 简单;想做一层转换过滤 [y for y in map(f, xs) if y > 0] 同样只调一次 f 且不产生泄漏。PEP 572 当年争议极大(直接导致 Guido 卸任 BDFL),焦点正是「消除重复」与「鼓励压行」的界线,实践中保持克制、收敛到几个公认清晰的模式即可。
  • 追问:f(x = 1)f(x := 1) 有什么区别? 完全不同的两件事。f(x = 1)传关键字参数:把 1 传给 f 的名为 x 的形参,调用方作用域里不会产生任何变量。f(x := 1)传位置参数:把表达式 x := 1 的值(即 1)作为第一个位置参数传进去,同时在调用方作用域里创建/覆盖一个局部变量 x = 1。一个字符之差,参数传递方式和副作用都变了,是很容易看走眼的坑。真实用途是「传参的同时留住这个值」,例如 if validate(data := load()):use(data);但在参数名恰好和某个形参同名时极具迷惑性,建议这种场合还是拆成两行写。
  • 追问::= 在推导式里到底怎么减少计算次数,能省多少? 关键在于旧写法 [f(x) for x in xs if f(x) > 0]f(x) 出现了两次:一次在过滤条件里、一次在输出表达式里,Python 不会自动做公共子表达式消除,所以对每个元素都要调两次 f,总共 2n 次。改成 [y for x in xs if (y := f(x)) > 0] 后,f 只在过滤条件里调用一次并把结果绑给 y,输出直接用 y,总共 n 次——调用次数减半。如果 f 是一次 30ms 的网络请求、xs 有 100 项,旧写法 6.0 秒、新写法 3.0 秒。更重要的是语义:如果 f 有副作用(写日志、计数、扣配额、生成随机数),调两次和调一次的结果完全不同,旧写法甚至可能让过滤用的值和最终输出的值不是同一个(比如 f 返回随机数或读取实时状态),这是隐蔽的正确性 bug,不只是性能问题。
  • 追问:为什么 Python 要设计成「顶层不能裸写 x := 1」? 为了防止 == 被误写成 := 之后代码「还能跑」。C 系语言里 if (x = 1) 是合法的(赋值表达式的值参与判断),把 == 手滑写成 = 的笔误制造了几十年隐蔽 bug,很多 C 编译器不得不加告警、程序员不得不发明「常量写左边」的 Yoda 写法来防御。Python 的策略是双保险:普通赋值 = 保持语句身份(if x = 1: 直接语法错误、编译期就挡住),同时把 := 限制在「确实需要表达式赋值」的位置——顶层裸写非法(顶层写 x = 1 就够了,允许 x := 1 只会造成两种写法并存的混乱)、不能给属性和下标赋值、没有 :+=。这样 := 出现的每一处都是程序员明确的意图表达,而不是可能的笔误。

八、加强记忆

海象运算符 :=(正式名「赋值表达式」,Python 3.8+,PEP 572)的全部意义是:让赋值具备「表达式」身份,从而能写进 if/while/推导式的条件位置。它不带来任何新能力,只消除三种重复:① 函数调两遍[f(x) for x in xs if f(x)>0][y for x in xs if (y := f(x))>0],调用次数由 2n 降到 n;若 f 有副作用,这还是正确性问题而不只是性能问题);② 读取写两遍while chunk := f.read(8192):,旧写法漏掉循环末尾那次读取就是死循环);③ 多一行临时变量if m := re.match(p, s):)。三条铁律:一是优先级最低,嵌在更大表达式里必须整体加括号——n := n + 1 > 10 会把布尔值赋给 n;二是判空优先 is not None——队列里合法的 0/""/[] 会让真值判断提前结束循环、静默丢数据(读文件例外,因为空值只可能出现在结束时);三是目标只能是简单变量名——属性、下标、解包、:+=、顶层裸写全部非法(这些限制是为了杜绝 == 误写成 := 的 C 式笔误)。作用域上它反常识:推导式的循环变量用完即消失,而 := 的绑定泄漏到包含推导式的外层作用域(有意设计,为了留住中间值),所以要用独特变量名以免静默覆盖外层同名变量;类体内的推导式里用 := 直接 SyntaxError,生成器表达式里则要等真正迭代到那一步变量才有值。还有一个易看走眼的坑:f(x = 1) 是传关键字参数,f(x := 1) 是传位置参数并顺带创建局部变量 x。最后判断该不该用只看一句:消除了重复就留下,只是压行就改回两行