Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

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

Table of Contents

Abstract

This PEP proposes adding two new operators.

  • The “None coalescing” 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 inverse if ... is None else ... in the same code blocks.
  • Avoids the often (mis-) used or to provide fallback values just for None.
  • 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


Source: https://github.com/python/peps/blob/main/peps/pep-0998.rst

Last modified: 2026-01-31 16:26:36 GMT