A person with purple hair holds a notebook and slots a card labeled MISSING into a large machine that feeds out long paper printouts, beside control panels and a Python logo.

Python 3.15 Preview: Sentinel Values

Sometimes your code needs to pass along a signal, like “no argument was passed” or “the stream has ended,” in the same place where it normally receives data. Any ordinary value you pick for that job, even None, might also show up as real data. That’s why, in Python, you’ve probably written or seen something like _MISSING = object() to create a sentinel value that your code treats as a signal rather than as data.

That idiom works, but its default representation clutters function signatures, provides no useful information to static type checkers, and fails identity checks after copying. Other approaches have limitations, too. To address all of these drawbacks at once, Python 3.15 adds a built-in sentinel type.

By the end of this tutorial, you’ll understand that:

  • Python 3.15 adds sentinel to the built-ins through PEP 661, so you don’t need to import the type.
  • A sentinel prints its own name, so it reads clearly in function signatures, help(), and inspect.signature().
  • Sentinels keep their identity through copy(), deepcopy(), and a pickle round trip.
  • A sentinel works as a type hint, following the precedent set by None.
  • None remains the recommended default unless None is legitimate data in your code, in which case you should use a sentinel.

To start, you’ll look at what a sentinel is and the existing options for creating one. Then you’ll meet Python 3.15’s sentinel built-in and work through the issues it solves.

Understand Sentinel Values in Python

Before you learn about the new built-in sentinel type, you’ll look at the problem it addresses in earlier Python versions. In this section, you’ll learn what a sentinel value is, recognize the ones you’ve been using for years without naming them, and see where existing approaches fall short. This context explains why Python adds sentinel to its built-in types.

Know What a Sentinel Value Is

A sentinel value is a value that an algorithm treats as a signal rather than as data. It marks a condition such as “terminating the iteration,” “shutting down the worker thread,” or “nothing was passed.” A sentinel is a marker that your code branches on, and it doesn’t carry any information about the data you’re processing.

Python uses sentinel values in many situations. For example, when you search a string and the target substring isn’t there, you get -1 back:

Language: Python
>>> "Hello, World!".find("z")
-1

In this example, -1 tells you that the substring isn’t there. In other words, your code should read it as a signal rather than as an index pointing to the target substring.

A sentinel can also be a unique object that you create for this purpose, ensuring that nothing else can ever equal it.

The essential idea is that a sentinel value should stay distinct from every legitimate value your data can take. This way, a loop over your data can check each value and branch when it encounters the sentinel:

Each value read is tested against the sentinel. A match ends the loop, while any other value is treated as data and processed before the next read.
A Sentinel Value Ending a Read Loop

Now that you know what a sentinel value is, you can start spotting the ones that Python has been handing you all along.

Spot the Sentinels Python Already Uses

You’ve probably been using sentinels since your early Python programs. Using None as the default value for optional arguments is a common example. It typically means “no value was passed.” In a data context, it may mean “no data.”

In the standard library, you’ll also find a variety of sentinels. Here are four modules and some of their sentinel values:

Language: Python
>>> import configparser
>>> configparser._UNSET
<object object at 0x7f6d134cc6f0>

>>> import inspect
>>> inspect.Parameter.empty
<class 'inspect._empty'>

>>> import typing
>>> typing.NoDefault
typing.NoDefault

>>> import unittest.mock
>>> unittest.mock.DEFAULT
sentinel.DEFAULT

Each of these four modules uses a different approach to sentinels:

Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Article

Already a member? Sign-In

Locked learning resources

The full article is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Article

Already a member? Sign-In

About Leodanis Pozo Ramos

Leodanis is a self-taught Python developer, educator, and technical writer with over 10 years of experience.

» More about Leodanis

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

What Do You Think?

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal.


Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!

Become a Member to join the conversation.

Keep Learning