PEP 998 – Coalescing operators
- Author:
- Marc Mueller
- Sponsor:
- Guido van Rossum <guido at python.org>
- Discussions-To:
- Pending
- Status:
- Draft
- Type:
- Standards Track
- Created:
- 20-Jan-2025
- Python-Version:
- 3.15
Abstract
This PEP proposes adding two new operators.
- The “
Nonecoalescing” operator?? - The “Coalescing assignment” operator
??=
The None coalescing operator evaluates the left hand side,
checks if it is not None and returns the result. If the value is
None, the right hand side is evaluated and returned.
The coalescing assignment operator will only assign the right hand side
if the left hand side evaluates to None.
They are roughly equivalent to:
# a ?? b
_t if ((_t := a) is not None) else b
# a ??= b
if a is None:
a = b
See the Specification section for more details.
Motivation
First officially proposed ten years ago in (the now deferred) PEP 505 the idea to add coalescing operators has been along for some time now, discussed at length in numerous threads, most recently in [1]. This PEP aims to capture the current state of discussion and proposes a specification for addition to the Python language. In contrast to PEP 505, it will only focus on the two coalescing operators. See the Deferred Ideas section for more details.
None coalescing operators are not a new invention. Several other
modern programming languages have so called “null coalescing”
operators, including TypeScript [2], ECMAScript (a.k.a. JavaScript)
[3] [4], C# [6], Dart [8] [9],
Swift [10], Kotlin [11], PHP [12] [13] and more.
The general idea is to provide a comparison operator which instead of
checking for truthiness, looks for None values.
Explicit checks for None
In Python None is often used to denote the absents of a value. As
such it it common to have to check if a value is None to do some
action or provide a fallback value. Python provides several options to
do that, each with their own advantages and disadvantages. One such
option is a conditional statement:
def func(val: str | None):
if val is not None:
s = val
else:
s = "fallback"
print(f"The value is {s.lower()}")
While the intend is quite clear, the conditional statement is verbose and often requires repeating the variable expression. An common alternative is the conditional expression:
def func(val: str | None):
s = val if val is not None else "fallback"
print(f"The value is {s.lower()}")
This is more concise. It is however still necessary to repeat the variable expression. If those get more complex, it is often common that developers choose to introduce a new temporary variable, splitting the variable from the conditional expression and as such end up unintentionally introducing additional complexity.
def func(obj):
val = obj.var.get_some_value()
s = val if val is not None else "fallback"
print(f"The value is {s.lower()}")
Furthermore, it is not unusual to see the condition being used in its “normal” and “inverted” form in the same code base, increasing the mental load while reading the code.
def func(obj):
val = obj.var.get_some_value()
s = "fallback" if val is None else val
print(f"The value is {s.lower()}")
Another often seen approach is to just forgo the safety of an explicit
conditional expression entirely and use a boolean or instead.
def func(obj):
s = obj.var.get_some_value() or "fallback"
print(f"The value is {s.lower()}")
This can and often does work fine though it hides potential error cases
which might get unnoticed now. By choosing or over is not None,
Python will check for truthiness instead of comparing the value to
None. It frequently works fine as None evaluates to False.
Errors can occur though if the variable being checked can be falsy for
values other then None, too. Common ones include 0, "",
(), [], {}, set() or custom objects which overwrite
__bool__.
The “None coalescing” operator can bridge this gab by adding an
alternative to or which explicitly checks only for None values.
Adding ?? can keep the expression concise while clearly
communicating the intend:
def func(obj):
s = obj.var.get_some_value() ?? "fallback"
print(f"The value is {s.lower()}")
Overwrite None values
Another use case, especially for function argument defaults, is to
overwrite the “old” None value.
def func(val: str | None):
if val is None:
val = "fallback"
print(f"The value is {val.lower()}")
A new ?= operator is introduced to make these conditional
assignments easier.
def func(val: str | None):
val ??= "fallback"
print(f"The value is {val.lower()}")
It avoids repeating the variable expression which can be especially helpful for more complex expressions.
def other(obj):
if obj.var.some_other_object.another_variable is not None:
obj.var.some_other_object.another_variable = "fallback"
# can be replaced with
obj.var.some_other_object.another_variable ??= "fallback"
Specification
The None coalescing operator
The ?? operator is added. It first evaluates the left hand side.
The result is cached, so that the expression is not evaluated again.
If the value is not None, it is returned. If it is None, the
right hand side expression is evaluated and returned instead.
# a ?? b
_t if ((_t := a) is not None) else b
Precedence
The precedence of ?? will be between or and conditional
expressions. Parentheses can be added as necessary to modify the
precedence in individual expressions. A few examples how implicit
parentheses would be placed:
# "" or None ?? 2
("" or None) ?? 2
# "Hello" if None ?? True else 0
"Hello" if (None ?? True) else 0
AST changes
A new CoalesceOp AST node is added. Similarly to BoolOp, it
stores a sequence of subexpressions as values.
expr = BoolOp(boolop op, expr* values)
| CoalesceOp(expr* values)
| ...
Grammar changes
A new ?? token is added, as well as a new coalesce rule. Every
rule which previously referenced the disjunction rule is updated
to refer to the coalesce rule instead.
coalesce:
| disjunction ('??' disjunction)+
| disjunction
disjunction:
| conjunction ('or' conjunction)+
| conjunction
The Coalescing assignment operator
The ??= operator is added. It performs a conditional assignment.
As such it will first evaluate the left hand side and check that the
value is None and only then assign the result from the right hand
side. If the first value is not None, the assignment is skipped.
# a ??= b
if a is None:
a = b
AST changes
A new CoalesceAssign AST node is added. Similarly to AugAssign,
it stores a target and value expression.
stmt = ...
| AugAssign(expr target, operator op, expr value)
...
| CoalesceAssign(expr target, expr value)
Grammar changes
A new ??= token is added. Additionally, the assignment rule
is extended to include the coalesce assignment.
assignment:
| NAME ':' expression ['=' annotated_rhs]
| ('(' single_target ')'
| single_subscript_attribute_target) ':' expression ['=' annotated_rhs]
| (star_targets '=')+ annotated_rhs !'=' [TYPE_COMMENT]
| single_target augassign ~ annotated_rhs
| single_target '??=' ~ annotated_rhs
Backwards Compatibility
The coalescing operators are opt-in. Existing programs will
continue to run as is. So far code which used either ?? or ??=
raised a SyntaxError.
Security Implications
There are no new security implications from this proposal.
How to Teach This
In a practical sense it might be helpful to think of the “None
coalescing” operator ?? as a special case for the conditional or
operator, with the caveat that ?? checks for is not None instead
of truthiness. As such it makes sense to include ?? when teaching
about the other conditional operators and and or.
The “coalescing assignment” ??= operator can be best thought of as
a conditional assignment operator. As it is closely related to ??
explaining these together would make sense.
Reference Implementation
A reference implementation is available at https://github.com/cdce8p/cpython/tree/pep-XXX. A online demo can be tested at https://pepXXX-demo.pages.dev/.
Deferred Ideas
None-aware access operators
PEP 505 also suggest the addition of the None-aware access
operators ?. and ?[ ]. As the coalescing operators have their
own use cases, the None-aware access operators were moved into a
separate document, see PEP-XXX. Both proposals can be adopted
independently of one another.
Rejected Ideas
Add new (soft-) keyword
Python does have a history of preferring keywords over symbols. For
example, while a lot of languages use && and || as conditional
operators, Python uses and and or respectively. As such it was
suggested to use a new (soft-) keyword, e.g. otherwise, instead of
??.
While the keywords and and or help avoid ambiguity with the
binary operators & and |, there is not a corresponding binary
operator for ??. Furthermore, both keywords are well established
in spoken and written language and as such immediately obvious to the
reader. Not to mention they are also quite short with just two and three
characters.
In comparison a new (soft-) keyword would likely not enjoy the same
benefits. There is no established short name for it, so while ideas
like otherwise could be added, they do not convey an inherit
meaning and therefore do not provide an immediate benefit. In contrast,
the ?? operator is well known in other major programming
languages.
Lastly, using a (soft-) keyword for the “coalescing assignment” operator poses additional questions and readability concerns.
a = otherwise b
Add ?? as a binary operator
PEP 505 originally suggested to add ?? as another binary operator.
As such it would have bound more tightly than the proposed
specification.
Though this would have worked fine, it would have suggested that ??
is similar to other binary operators like + or **. This is not
the case. While binary operators first evaluate the left and right
hand side before performing the operation, for ?? only the left hand
side is evaluated if the values is not None. As such the
“None coalescing” operator is much more closely related to the
conditional operators or and and which also short-circuit the
expression for truthy and falsy values respectively.
Furthermore, setting the precedence between or and conditional
expressions matches other languages which have implemented the operator,
like JS [5] and C# [7].
Add ??= as AugAssign
PEP 505 also suggested to add ??= as an AugAssign node.
So far AugAssign is only used for binary operators, as such including
??= which is a conditional assignment operator would be confusing.
Furthermore, AugAssign statements always evaluate the left and
right hand side, without any short-circuiting. This is a major
difference compared to coalescing assignments.
Common objections
Just use a conditional expression
The coalescing operators can be considered syntactic sugar for existing conditional expressions and statements. As such some questioned whether they would add anything meaningful to the language as a whole.
As shown in the Motivation section, there are clear benefits to using the coalescing operators. To summarize them again:
- They help avoid repeating the variable expression or having to introduce a temporary variable.
- Clear control flow, no more
if ... is not None else ...and the inverseif ... is None else ...in the same code blocks. - Avoids the often (mis-) used
orto provide fallback values just forNone. - More concise while also being more explicit.
Proliferation of None in code bases
One of the reasons why PEP 505 stalled was that some expressed their
concern how coalescing and None-aware operators will effect the code
written by developers. If it is easier to work with None values,
this will encourage developers to use them more. They believe that
e.g. returning an optional None value from a function is usually an
anti-pattern. In an ideal world the use of None would be limited as
much as possible, for example with early data validation.
It is certainly true that new language features effect how the language
as a whole develops. Therefore any changes should be considered carefully.
However, just because None represents an anti-pattern for some, has
not prevented the community as a whole from using it extensively. Rather
the lack of coalescing operators has stopped developers from writing
concise expressions and instead often leads to more complex code or such
which can contain subtly errors, see the Motivation section for more
details.
None is not special enough
Some mentioned that None is not special enough to warrant dedicated
operators.
Coalescing operators have been added to a number of other modern
programming languages. Furthermore, adding ?? and ??= is
something which was suggested numerous times since PEP 505 was
first proposed ten years ago.
In Python None is frequently used to indicate the absence of
something better or a missing value. As such it is common to look
specifically for None values, for example, to provide a default
or fallback value.
Footnotes
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.
Source: https://github.com/python/peps/blob/main/peps/pep-0998.rst
Last modified: 2026-01-31 16:26:36 GMT