内容提要
该PEP提议为Python添加None合并运算符??和??=。??在左侧非None时返回左侧,否则返回右侧;??=仅在左侧为None时赋值。这能简化默认值处理,避免重复表达式,减少副作用风险。类似特性已存在于TypeScript、JavaScript、C#等语言。
延伸解读
与 or 运算符的关键区别
?? 运算符与 or 类似,但检查的是 is not None 而非真值性。这意味着当左侧为 0、空字符串等假值时,or 会返回右侧,而 ?? 会返回左侧。这一区别在需要区分 None 和其他假值的场景中至关重要,例如处理数值 0 或空列表作为有效值时。
运算符优先级与短路行为
?? 的优先级低于 or 但高于条件表达式,因此 x or y ?? 2 会解析为 (x or y) ?? 2。与二元运算符不同,?? 具有短路特性:仅当左侧为 None 时才评估右侧。这有助于避免不必要的副作用,并提高代码效率。
??= 的求值语义与线程安全
??= 是条件赋值运算符,仅当左侧为 None 时才评估并赋值右侧。左侧的子表达式只求值一次并缓存,类似于增强赋值。该操作不是原子的,多线程环境下可能产生竞态条件,需使用锁等同步机制。
设计选择与语言对比
PEP 824 选择符号 ?? 而非关键字,因为该符号在 TypeScript、JavaScript、C# 等语言中已广泛使用,且 Python 中不存在对应的二元运算符,不会引起歧义。与 PEP 505 不同,本 PEP 仅关注 None 合并运算符,将 None 感知访问运算符移至 PEP 823,两者可独立采用。
Q&A
Python 的 None 合并运算符 ?? 和 ??= 分别是什么?
?? 是 None 合并运算符:如果左侧不是 None,则返回左侧;否则返回右侧。??= 是 None 合并赋值运算符:仅当左侧为 None 时,才将右侧的值赋给左侧。
为什么 Python 需要引入 ?? 和 ??= 运算符?
因为用 is None 检查来提供默认值或回退值很冗长,且容易重复表达式,可能引发副作用。?? 和 ??= 能简化代码,避免重复,并清晰表达意图。
?? 运算符的求值顺序和短路行为是怎样的?
?? 首先求值左侧,并缓存结果。如果结果不是 None,则返回缓存的结果;如果是 None,则求值右侧并返回。右侧仅在左侧为 None 时才会被求值。
?? 运算符的优先级如何?与 or 和条件表达式相比呢?
?? 的优先级低于 or,但高于条件表达式(如 x if cond else y)。必要时可以用括号改变优先级。
??= 运算符在赋值时如何处理左侧的子表达式?
左侧的子表达式只求值一次并缓存,类似于增强赋值。例如 a.b.c ??= d 中,a.b 只求值一次。
其他哪些编程语言已经有类似的 null 合并运算符?
TypeScript、ECMAScript(JavaScript)、C#、Dart、Swift、Kotlin、PHP 等语言都有 null 合并运算符。
?? 和 ??= 与 PEP 505 有什么关系?
PEP 505 最早提出添加 None 合并运算符,但已被推迟。本 PEP 只关注 ?? 和 ??= 两个运算符,而将 None 感知访问运算符 ?. 和 ?[ ] 移至 PEP 823。
??= 运算符是原子操作吗?为什么?
不是原子操作。曾有人提议使其原子化以避免竞态条件,但被拒绝,因为其他多步运算符(如 +=)也不是原子的。如有竞态问题,应使用锁等同步原语。