[Pre-PEP] None-coalescing operator ?? and null-coalescing assignment ??=
Author: Rainwalker
Target version: Python 3.16 (or later)
Status: Pre‑PEP (discussion)
Note: This is a narrow subset of PEP 505 (no ?. or ?[]).
Problem
Currently, the standard way to provide a default value when something is None is:
value = get_value() if get_value() is not None else default
This has two major issues:
- Redundancy – the
expression get_value()is written twice. When the expression is long or has side effects, this becomes error‑prone and hurts readability. - Double evaluation & side effects – if
get_value()is a function call, it is executed twice. Worse, if it mutates state (e.g.,list.pop()), the second evaluation may produce a different value or corrupt data.
A workaround is to store the result in a temporary variable:
tmp = get_value()
value = tmp if tmp is not None else default
But this adds an extra line and still feels verbose.
The or operator is not suitable because it treats all falsy values (0, False, "", [], etc.) as triggers, not only None:
value = get_value() or default # wrong when get_value() returns 0 or False
Similarly, assigning a default value only when a variable is None requires an explicit check:
if x is None:
x = default
This is verbose and breaks the flow of expression‑oriented code.
Proposal
Introduce two new operators:
- None‑coalescing operator
??
value = get_value() ?? default
- Evaluates the left‑hand side.
- If the left‑hand side is not
None, that value is returned and the right‑hand side is not evaluated (short‑circuiting). - If the left‑hand side is
None, the right‑hand side is evaluated and returned.
Chainability
Multiple ?? operators can be chained. The expression returns the first non‑None value:
result = a ?? b ?? c ?? d
This is equivalent to:
if a is not None:
result = a
elif b is not None:
result = b
elif c is not None:
result = c
else:
result = d
Precedence
The ?? operator should have the same precedence as or. For example:
a + b ?? c * d # parsed as (a + b) ?? (c * d)
None-coalescing assignment ??=
x ??= default
This is a shorthand for:
if x is None:
x = default
However, unlike other augmented assignments (+=, |=, etc.), ??= evaluates the right‑hand side only if the left‑hand side is None (short‑circuiting). It does not return a value – it is a statement, just like +=.
Examples
# ?? – expression
first_title = web_search("python") ?? "No results found"
user = db.fetch(user_id) ?? default_user
data = api.get(key) ?? cache.get(key) ?? fallback_value
# Avoid double evaluation
value = expensive() ?? default # instead of expensive() if expensive() is not None else default
# Safe with side‑effect functions
data = stack.pop() ?? backup_data
# ??= – assignment
key ??= compute_default(key) # only compute if missing
config.timeout ??= 5.0
user.nickname ??= "Anonymous"
# Works with any assignment target (variable, attribute, subscript)
obj.attr ??= 42
lst[index] ??= 0
Why this belongs in Python
- Other mainstream languages already have
??– C#, JavaScript, PHP, Swift, Kotlin (Elvis operator). Many of them also have??=(e.g., C#, JavaScript, PHP). This demonstrates utility and low learning curve. - Addresses real, frequent patterns – Many codebases contain
x if x is not None else yorif x is None: x = default. - Does not break backward compatibility –
??and??=are currently syntax errors. ??=is a natural extension – just like+=complements+,??=complements??.
Open questions for discussion
- Precedence of
??– Should it have the same precedence asor? (Proposed: yes.) - Chaining of
??– Shoulda ?? b ?? cbe allowed? (Proposed: yes.) - Interaction with await – Should
await foo() ?? defaultbe allowed? (Proposed: yes.) ??=for attributes/subscripts – Should it work withobj.attr ??= val and lst[i] ??= val? (Proposed: yes – same as regular assignment.)
Rejected alternatives
or– does not distinguish None from other falsy values.- if‑expression – verbose and repeats the expression.
- coalesce built‑in function – cannot provide short‑circuiting without a lambda, which harms readability.
- Separate PEP for
??=– better to propose together as a consistent pair.
Implementation sketch
The change would require:
- Adding new tokens
??and??=to the grammar. - Adding new AST nodes (or reusing
BinOpwith a new operator for??; a new assignment node for??=). - Implementing short‑circuit evaluation for both.
- Updating the precedence table.
No changes to existing syntax or semantics are needed.
Questions for the community
- Are
??and??=desirable enough to warrant new operators? - Should we adopt the exact same semantics as in C#/JavaScript (only None triggers fallback)?
- Are there any hidden pitfalls with short‑circuiting or precedence?
Thank you for reading! I look forward to your feedback.