Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 66 additions & 9 deletions peps/pep-0824.rst
Original file line number Diff line number Diff line change
Expand Up @@ -180,21 +180,36 @@ If the value is ``not None``, the cached result is returned. If it is
# a ?? b
_t if ((_t := a) is not None) else b

.. _`spec-chaining`:

Chaining
********

An expression like ``a ?? b ?? c ?? d`` evaluates its subexpressions
(``a``, ``b``, etc.) from left to right until it finds a value that is
not ``None`` or reaches the end. If all values are ``None``, the result
is ``None``.

Like ``and`` and ``or``, ``??`` is **associative**: ``(a ?? b) ?? c``
has the same meaning as ``a ?? (b ?? c)``.

Precedence
**********

The precedence of ``??`` will be lower than ``or`` but higher than
conditional expressions. Parentheses can be added as necessary to
modify the precedence of individual expressions. A few examples of
how implicit parentheses would be placed:
modify the precedence of individual expressions. Setting the precedence
like this means ``or`` can easily be replaced with ``??`` where
appropriate without any surprises. A few examples of how implicit
parentheses would be placed:

.. code-block:: py824

# x or y ?? 2
(x or y) ?? 2

# "Hello" if x ?? True else 0
"Hello" if (x ?? True) else 0
# a ?? b if c else d
(a ?? b) if c else d

AST changes
***********
Expand Down Expand Up @@ -242,6 +257,9 @@ and assignment of the right-hand side are skipped.
if a is None:
a = b

This has the same effect as ``a = a ?? b``, except that there is no
assignment when ``a`` is not ``None``.

Subexpression caching
*********************

Expand Down Expand Up @@ -337,6 +355,8 @@ assignment, it evaluates the target only once. For example, in
``a[index()] ??= b``, ``index()`` is called only once. The right-hand
side is evaluated only when the target's value is ``None``.

.. _`how-to-chaining`:

Chaining
--------

Expand All @@ -345,8 +365,8 @@ An expression like ``a ?? b ?? c ?? d`` evaluates its subexpressions
not ``None`` or reaches the end. If all values are ``None``, the result
is ``None``.

Like ``or``, ``??`` is associative: ``(a ?? b) ?? c`` has the same
meaning as ``a ?? (b ?? c)``.
Like ``and`` and ``or``, ``??`` is **associative**: ``(a ?? b) ?? c``
has the same meaning as ``a ?? (b ?? c)``.

Operator precedence
-------------------
Expand Down Expand Up @@ -446,7 +466,7 @@ Deferred Ideas
operators ``?.`` and ``?[ ]``. As the "``None``-coalescing" operators
have their own use cases, the "``None``-aware access" operators were
moved into a separate document, see :pep:`823`. Both proposals can be
adopted independently of each other.
adopted independently of each other, though they do work well together.

Other conditional assignment operators
--------------------------------------
Expand All @@ -458,6 +478,23 @@ conditional assignment operators ``&&=`` and ``||=`` in other programming
languages such as ECMAScript (a.k.a. JavaScript) [#js_and_assign]_
[#js_or_assign]_, Ruby [#ruby]_ and Perl [#perl]_.

Add ``operator.coalesce``
-------------------------

It was suggested to add a new ``coalesce`` function to the ``operator``
module. This was deferred since the other boolean operators ``and`` and
``or`` do not have a corresponding function either yet.
:py:func:`operator.and_` and :py:func:`operator.or_` exist but those
map to the binary operations ``&`` and ``|``.

Add a combined ``??:=`` operator
--------------------------------

Another idea was to add an operator which combines ``??=`` and ``:=``
into one: ``??:=``. It could be used like an assignment expression but
would only assign values if the target is ``None``. This is outside the
scope of this PEP.


Rejected Ideas
==============
Expand Down Expand Up @@ -539,6 +576,18 @@ This was rejected since other operators with multiple steps, for example
issue, existing synchronization primitives like locks should be used
instead.

Add protocol to customize coalesce behavior
-------------------------------------------

It was discussed to make the coalesce behavior customizable via a
protocol.

One of the advantages of ``??`` is that it is limited to only ``None``.
This avoids the ambiguity that would come with using ``or`` where it is
not always immediately obvious if a value will be truthy, especially
considering that objects can define a custom :py:meth:`object.__bool__`
method.


Common objections
=================
Expand Down Expand Up @@ -619,8 +668,8 @@ Use custom sentinels instead of ``None``
----------------------------------------

In Python 3.15, :pep:`661` added the option to define custom sentinels
using ``sentinel(...)``. This addressed an issue in cases where ``None``
itself is a valid value and thus could not be used as sentinel.
using :py:class:`sentinel`. This addressed an issue in cases where
``None`` itself is a valid value and thus could not be used as sentinel.

In general though, ``None`` will still be preferred if a sentinel is
needed, simply because it already exists for that exact purpose and
Expand Down Expand Up @@ -687,6 +736,14 @@ Footnotes
Change History
==============

* 31-Oct-2026:

- Added section on `Chaining <spec-chaining_>`_ to the specification.
- Added `Add operator.coalesce`_ and `Add a combined ??:= operator`_
as deferred ideas.
- Added `Add protocol to customize coalesce behavior`_ as rejected
idea.

* 20-Oct-2026: Turned `How to Teach This`_ into a mini-tutorial.


Expand Down
Loading