While the Northern Hemisphere warms up for summer, Python 3.15 went the other way with its beta 1 feature freeze 🥶. Since May 7, the list of what will be included in the next release is final. That list includes a brand-new sentinel built-in that finally standardizes a pattern Python developers have been hand-rolling for decades.
And while AI kept writing code, buggy or not, developers also directed it to look for bugs in code that had been sitting untouched for years. The results were hundreds of bug fixes in Python’s C extensions and in Firefox. Meanwhile, in a quieter corner of the ecosystem, Pydantic forked httpx, kicking off one of the more interesting governance stories of the year.
Time to dig into the Python news from the past month!
Join Now: Click here to join the Real Python Newsletter and you’ll never miss another Python tutorial, course, or news update.
Python Releases and PEP Highlights
The 3.15 release of CPython crossed from alpha into beta, which means its feature set is now frozen, and the Steering Council cleared out a backlog of proposals before the gate closed. Two of those changes will touch the code you write every day.
Beta 1 Marks the 3.15 Feature Freeze
Last month, the eighth and final alpha rolled out as the runway to the beta phase. With Python 3.15.0b1 on May 7 came the feature freeze, which means that from here until the final release of 3.15, the core team works only on bug fixes and polishing.
That makes the beta releases a good moment to step back and look at the headline features of 3.15, which are now locked:
- Explicit lazy imports (PEP 810) for faster startup
- A
frozendictbuilt-in (PEP 814) for immutable mappings - A
sentinelbuilt-in (PEP 661), which you’ll dig into below - Unpacking in comprehensions (PEP 798)
- UTF-8 as the default encoding (PEP 686)
- A stable ABI for free-threaded builds (PEP 803), plus C-API modernization (PEPs 820 and 793) that should make it easier to write C extensions that work across Python versions
- A new sampling profiler in the standard library (PEP 799) for low-overhead profiling
The JIT compiler also gets faster, with the beta announcement citing an 8–9 percent geometric-mean improvement on x86-64 Linux. If you’ve been putting off testing your code against 3.15, then now is the time to get started! The API surface won’t shift under you anymore, and your feedback will help catch regressions before the release candidate phase.
Note: Beta builds are for testing, not production. Install the pre-release version, run your test suite against 3.15, and report anything that breaks while there’s still time to fix it before the release candidate.
The first round of improvements already landed with beta 2 on June 2, and the next big checkpoint is the release candidate phase on August 4, with the final release expected, as usual, this fall.
A Built-in sentinel Lands in Python 3.15
Here’s the new feature that you’ll likely want to reach for. If you’ve ever needed to tell the difference between a caller passing None and a caller passing nothing at all, then you’ve probably written something like this:
_MISSING = object()
def update(value=_MISSING):
if value is _MISSING:
... # No value was provided
It works, but it has rough edges. The repr() is an unhelpful <object object at 0x7f...>, the marker can’t be used cleanly in type annotations, and its identity doesn’t survive copying or pickling. PEP 661 replaces the idiom with a new sentinel built-in:
MISSING = sentinel("MISSING")
def update(value: int | MISSING = MISSING) -> None:
if value is MISSING:
... # No value was provided
The signature is sentinel(name, /, *, repr=None), and the result is a unique truthy object whose default repr() is the name you gave it, so MISSING shows up as MISSING in tracebacks instead of a memory address.
Note: Sentinels and None solve related but different problems. If you’re still fuzzy on when None is the right tool, then Real Python’s guide to Python’s None is worth revisiting.
Because the sentinel is its own type, you can drop it straight into annotations like int | MISSING without reaching for Literal. The PEP was first submitted back in 2021, so it’s satisfying to see it cross the finish line.
PEP 829 Graduates From Draft to Accepted
Last month’s roundup featured PEP 829 while it was still a draft. It’s since been accepted for Python 3.15, so the change is now official.
As a quick recap, .pth files in your site-packages directory can do two things:
- Extend
sys.path - Run arbitrary code through
importlines that Python feeds directly toexec()at startup


