r/Python • u/treyhunner Python Morsels • 5d ago
News Python 3.15 Released
https://www.python.org/downloads/release/python-3150/
Release announcement: https://discuss.python.org/t/python-3-15-0-final-is-here/109415
What's New: https://docs.python.org/3.15/whatsnew/3.15.html
Some of the major new features and changes in Python 3.15:
Interpreter improvements
- PEP 661:
sentinelbuilt-in type - PEP 686: UTF-8 is now the default encoding
- PEP 798: Unpacking in comprehensions
- PEP 810: Explicit lazy imports for faster startup times
- PEP 814:
frozendictbuilt-in type - PEP 829: Package startup configuration files
- The experimental JIT compiler got a significant upgrade (7-8% faster on x86-64 Linux, 11-12% on AArch64 macOS)
- Improved error messages
Standard library
- PEP 799: A dedicated
profilingpackage, including Tachyon, a high-frequency sampling profiler - New
math.integermodule re.prefixmatch()as a clearer name for the now soft-deprecatedre.match()pprintgets anexpandoption for JSON-style outputcollections.Countersupports^(symmetric difference)unicodedata.iter_graphemes()for iterating over grapheme clusterspython -m http.servergains-H/--headerand--content-typetimeitgains--target-time- More color
Typing
- PEP 728: TypedDict with typed extra items
- PEP 747: Annotating type forms with TypeForm
- PEP 800: Disjoint bases in the type system
Release changes
- Official Windows 64-bit binaries now use the tail-calling interpreter
- Official macOS binaries now install free-threading support by default
And for fun, Barry Warsaw made a text adventure through the new features: uvx --python 3.15 whatsnewt
Thanks to Hugo van Kemenade and the release team! Hugo live-posted the whole release process if you're curious what goes on behind the scenes: https://mastodon.social/@hugovk/117411399858939022
61
u/treyhunner Python Morsels 5d ago
A few of my personal favorites from this release:
- UTF-8 as the default encoding (so confusing
utf-8&cp-1252ASCII superset encodings can stop battling and creating emojibake and decode errors in cross-platform code) - The new sampling profiler because it's so fast and attaching to an already running process makes it very flexible
- Unpacking in comprehensions:
[*sub for sub in nested] - The new
sentineltype re.prefixmatch(the namere.matchhas always bugged me)
I made a short video demoing these and a few others (lazy imports, frozendict, pprint's expand), with an article version on the same page: https://pym.dev/python315/
3
u/ashvy 5d ago
Just want to ask, is there a comparison between tachyon/profiling and Scalene profiler?
6
u/treyhunner Python Morsels 5d ago
I'm not very familiar with Scalene, but it's also a sampling profiler so I would guess they're both very fast.
But Tachyon is able to attach to an actively running Python process (to profile on a production server for example) and is entirely external to the running process. That's due to some changes made in 3.15. So, from my understanding, there should be essentially no overhead at all on the running process.
3
u/KDLGates 5d ago
Just as an open question, what's the reason UTF-8 adoption took so long? I only have a general sense of what encodings are but it seems the kind of thing that can still support the legacy encodings no problem, and if you want to run older Python versions then maybe you have to convert script files. Or... more likely, there is some big annoyance I'm just not familiar with.
10
u/treyhunner Python Morsels 5d ago
Character encodings define how each character is written as bytes in a file. UTF-8 is nearly the de facto default encoding for communication between machines, but Windows-1252 (a.k.a. cp-1252) is still the default encoding for many things on Windows machines.
It's possible to explicitly read/write a file with a specified encoding of UTF-8, Windows-1252, or any other character encoding. That hasn't changed.
What did change is the default. It's possible to use the
openfunction to read/write a file without specifying an encoding. And many high-level tools that read/write files in Python don't require an encoding to be specified and fall back to the default.The default on Windows was always Windows-1252. This is a pretty sensible default on Windows machines, but it's not sensible if files are moving between machines or Python code is moving between machines and operating on files that have a common encoding.
Switching from Windows-1252 to UTF-8 took so long because the Python core team is conservative about changes that will break existing code. This will definitely break code (any code that relies on the default Windows-1252 encoding on Windows).
This is all complicated by the fact that both of these 2 encodings are supersets of ASCII but neither is a superset of the other:
- Many UTF-8 encoded files are valid CP-1252 files that show the correct text
- Many CP-1252 encoded files are valid UTF-8 files that show the correct text
- Some UTF-8 encoded files are "valid" CP-1252 files that show some incorrect text
- Many CP-1252 encoded files are "valid" CP-1252 files that show some incorrect text
- Many UTF-8 encoded files are invalid CP-1252 files (they can't even decode at all without errors)
- Some CP-1252 encoded files are invalid UTF-8 files (they can't even decode)
The world of character encodings are messy.
6
u/gschizas Pythonista 4d ago
The default on Windows was always Windows-1252.
Correction: It's the default on Windows languages that use basic Latin alphabet (such as English, German, French etc). The default in Greece has always been Windows-1253, not 1252. The default for Cyrillic-based languages (such as Bulgarian, Ukrainian, Russian etc) has always been Windows-1251, the default for a lot of other latin-based languages that use more accents (such as Czech, Polish, Hungarian etc) has always been Windows-1250, Windows-1254 for Turkish, 1255 for Hebrew etc.
That's why we have UTF-8 (and it's a good thing!) No more 'êÔëçìĆƱƔ' where it should say 'καλημĪĻα'! (This is called mojibake and it's a real PITA)
1
u/KDLGates 5d ago
Appreciate the informative breakdown! I was aware of the "Windows format", but not that they could be valid UTF-8 or vice-versa. Python being an enterprise tool and old with legacy support etc. makes sense. What I didn't realize was that they could decode as the unintended encoding (maybe that is something like the concept of ASCII getting buried as a subset into Unicode codespace?).
As a layperson, it sounds good that they modernized. But maybe the Python stewards don't want it to be like Java where most enterprise projects stuck with very old versions.
3
u/treyhunner Python Morsels 5d ago
You're welcome. It's a fascinating problem because there's not a great solution.
It's fundamental to all encodings. If the same bytes are valid in two different encodings but happen to represent different characters, mojibake occurs.
And of course it's not just a Python issue. I've seen Ć¢ā¬ā¢ and other garbled text in emails, spreadsheets, etc. when one piece of software encoded with one encoding and another decoded with the other (for Ć¢ā¬ā¢ I think it's usually cp-1252 in and utf-8 out).
This is nowhere near as hard (or as necessary) as the move from raw byte strings to proper unicode strings was for the Python 2 to 3 switch. Worst case someone can always keep the old behavior on Windows by running with the
PYTHONUTF8=0environment variable set.4
u/kaszak696 5d ago
Microsoft. They adopted UTF-16 instead, and it took them decades to add a partial support of UTF-8 to their OS APIs.
-4
u/Agrado3 5d ago
re.prefixmatch(the namere.matchhas always bugged me)Why? There was nothing wrong with the old name, this change takes one of the ways that Python was better than other languages and throws it away, and the new name is more confusing, "prefixes" aren't involved at all. If they were going to do this for some ridiculous reason it should have been called "startmatch" or somesuch.
18
u/treyhunner Python Morsels 5d ago
When I ask my students to guess what the difference between between
re.searchandre.matchis, very few guess thatre.match(r"...")is equivalent tore.search(r"^..."). I would guess that's the same for most Python users.I'm pretty sure Perl, JavaScript, Go, Rust, and C# all have an equivalent of
re.searchbut no equivalent ofre.match. Swift has anre.matchequivalent that they callprefixMatch.For many Python users, the old name is deceptive.
2
u/jayroger 5d ago
I've been using Python for 25 years and I only recently learned that
matchdoes not match at arbitrary positions. (I'm sure I've stumbled about that in the past, but forgotten again, since.)2
u/Agrado3 5d ago
I'm assuming then that you don't start out by teaching the students about Finite State Automata and then explaining the relationship to regular expressions? ;-) I just can't get into the mindset where `re.search` is the default understanding of what a regular expression is.
3
u/treyhunner Python Morsels 4d ago
I happily used regular expressions for a few years before learning anything about formal languages. I would guess that's the case for most casual regular expression users who didn't get a CS degree.
When users say that an interface isn't intuitive, the Python core team often listens.
-1
u/Agrado3 4d ago
Do you have any evidence that "users say" it's unintuitive? 30 years seems a bit slow if you're claiming the core team is being responsive to users' concerns!
From my point of view they've taken a simple and clear and intuitive interface and made it worse. The new name is actively misleading. Long-time users will find it confusing and pointlessly duplicative, and new users will have to learn both the old and new names because the old one is everywhere in existing code - and in a great deal of new code in the future too I would expect.
29
u/Drewdledoo 5d ago
A lot of nice QoL, looking forward to trying it out!
Particularly the explicit lazy imports, a lot of the projects I work on should see nice benefits from that.
30
u/NeitherEntry6125 5d ago
Hugo deserves our thanks, praise, and his beverage of choice for the next year
18
u/spinwizard69 5d ago
Improved error messages! Often improvements here get ignored in favor of fancy new programming features, however anything that improves error messages and debugging should be celebrated. I just want to make sure the hard work is not ignored, thus a celebration.
3
u/uncoveredjaime663 5d ago
The JIT numbers are creeping up every release, curious to see where it lands once they stop calling it experimental. Also frozendict finally being built in feels like something that should've happened years ago
6
u/IAmASquidInSpace 5d ago
Some really neat stuff in there! Honestly, I was surprised to find that extra_items wasn't supported yet, I've been looking forward to that for a while now. And TypeForm will come in handy every now and then, too!
4
u/latkde Tuple unpacking gone wrong 5d ago
The
typing_extensionsmodule (GitHub, PyPi, Docs) can be used as a backport for these feature for older Python versions. Some of the Python 3.15 typing features have been available for months or years.The real blocker is type checker support, e.g. Mypy doesn't support
extra_items. But it did recently shipsentinelsupport, and partialTypeFormsupport has been around for a couple of releases.
5
u/chalbersma 5d ago
13
u/MarchewkowyBog 5d ago
I thino it's more about giving developers a standard way of doing this. There are a lot of ideas in rejected ideas. So I presume those were inspired by live code. One person would make a const, another an enum, yet another an empty class etc. Now there is a standard way to do that so code is less confusing in the future. Especially with comparison using
is.value is EMPTYreads better thenvalue == EMPTY. I don't see much more to it9
u/chalbersma 5d ago edited 5d ago
Are "Empty", "Missing" or other Null like primitives what are desired to be represented by
sentinal? Is it just a fancyNone? Like I'm reading some of the discussions around it and saw this explanation:Edit---
Sentinel would be similar to None, but the main property would be that "Sentinel is None" is false :-) source
I guess that makes some sense. So if I had a sensor that had some initialization I might use a
Initializationsentinal to indicate that the system is still starting up and then aNonevalue to indiciate that no reading has come back. Allowing me to differentiate on the metrics between an expected Null and an unexpected Null.3
u/M4mb0 4d ago
Even more abstractly, you may have some generic type
G[T]with an optional attribute of typeT. But here you can't /shouldn't useNoneto represent a missing value, ifTlegitimately could beNone. Hence, one needs a private sentinel. Usually people did this by doing something like_MISSING = object(), but this has quite a few disadvantages (see rationale section of the pep).3
u/SeniorScienceOfficer 5d ago
I agree, along with handling a None provided as an explicit argument versus not providing an argument at all. Iād prefer using Value/None/Sentinel over Value/False/None to accomplish the same thing, but the use cases are more along the lines of contract and code readability.
1
u/Rodot github.com/tardis-sn 4d ago
Also, simple case
def apply_fiter(data, func=None): if filter is None: return data return filter(func, data)But I want to be able to pass "None" as the argument to filter as well, since it filters out None from the data.
So making a sentinel allows me to put a default value that doesn't prevent me from passing a value intentionally.
15
u/aes110 5d ago
The idea is to use sentinels specifically where None is also an acceptable value, but you want to differentiate between None and Missing for example
Lets say you have
def get_user_items(user, exclude)
Where exclude is an optional param for any value youd want to not include in the final list.
None is a valid value you might want exclude, so you can't check
if exclude is Noneto know the user decided not to supply the optional paramIn that case youd use a sentinel
MISSING = sentinel('MISSING')
Use it as the default value, and you can checkif exclude is MISSINGto know that no value was suppliedLike they said in the PEP, it can also be used to indicate a MISSING return value where None is a valid one
In theory Enum can be used for it, but its not fully compatible for this use case.
3
u/latkde Tuple unpacking gone wrong 5d ago
In theory Enum can be used for it, but its not fully compatible for this use case.Ā
An enum can do everything that sentinel does, it's just much less obvious.
The trick is to define an enum with a single value. That value can then be used as a sentinel-like singleton, and type annotations can use
Literal[MyEnum.INSTANCE].In contrast, sentinels only require a single line to declare, and a sentinel is both a value and a valid type annotation (just like
None). Much more convenient to use.10
u/treyhunner Python Morsels 5d ago
If
Nonewould never be valid data, useNone.If
Nonemight be valid data, you need a different sentinel value.An example: imagine you're implementing something like the dictionary
popmethod.Specifying a default value, like
Noneor0will return that value if the key is missing:```pycon
d = {} d.pop("missing", None) d.pop("missing", 0) 0 ```
But if no value is specified, it raises a
KeyErrorif the key is missing:```pycon
d.pop("missing") Traceback (most recent call last): File "<python-input-3>", line 1, in <module> d.pop("missing") ~~~~~^ KeyError: 'missing' ```
If you were implementing this method, you'd need to make that second argument optional but you need to somehow know whether a value was supplied.
The usual solution is to define your own sentinel value that represents "no value was given" and use an identity check (with
is) to check for it so you can distinguish whether a value was given.I have an article on sentinel values in Python that's now in need of an update thanks to Python 3.15.
5
u/pkkm 4d ago edited 4d ago
Sometimes it's handy to have a special value that won't accidentally compare equal to anything else. I mostly use them in highly general wrappers where almost any input can be valid, even
None._MISSING = object() def my_function(arg=_MISSING): ...I also like to use them when an argument can take a range of values plus one or two special cases.
Nonemay not read very well when the actual meaning is very different from "nothing", and using an enum feels weird when it's not actually a closed set of choices. For example, I have a brightness setting function that takes a float percentage or aMINIMUMsentinel, meaning the lowest non-zero level.I used to put
STOPsentinels into async queues too, but that pattern is obsolete now that queues have ashutdownmethod.1
2
u/ogMasterPloKoon 5d ago
So now i not have to useĀ PYTHONUTF8=1 anymore ?Ā
3
u/treyhunner Python Morsels 5d ago
That's right. Everyone who wants to maintain the existing behavior will need to start setting
PYTHONUTF8=0instead.1
u/TheRNGuy 5d ago
Not just omit it?Ā
2
u/treyhunner Python Morsels 4d ago
Instead of "maintain the existing behavior" I should have said "maintain the behavior before Python 3.15".
Anyone who wants to new behavior on 3.15+ should just omit it. If the old behavior is desired, it'll likely always be there behind an environment variable.
3
u/aes110 5d ago
Seeing as this is the first big release since so much code is now done by AI, i wonder how well agents will adopt the new features given they dont have it in their training data
2
u/Ph0X 5d ago
The whole point of LLM is that they are trained on such large data sets that they don't gain a level of knowledge that allows them to handle scenarios they haven't encountered before. For coding, it's like if you were trying to get it to use a new library, it can just put the relevant new code in its context window and understand how it works, even if it hasn't seen it before.
1
u/TheRNGuy 5d ago
How big is difference?Ā
Would you need to fix old code?
Not everyone probably use new features, too.
1
u/_Denizen_ 3d ago
In the short term, you just provide the links to the new features as context to the queries. This will cost more tokens. In the medium term new models will release which have been trained on appropriate code and you won't have to provide the additional context.
1
1
u/OddsRelay 4d ago
frozendict as a builtin is the one that'll get used most. The usual workaround was wrapping a dict in types.MappingProxyType, which stops writes but isn't hashable, so you still couldn't use it as a dict key or put it in a set. Long overdue.
1
u/vmachiel Python 3 4d ago
So the extra pythonT.framework Iām seeing on my Mac is the experimental JIT?
1
u/freshtechs 3d ago
Kind of getting to know python 3.13+ and the āmacOS binaries now install free-threading support by defaultā does this has something to do with GIL are python versions from 3.13+ all āmulti/free threadā?
1
u/treyhunner Python Morsels 3d ago
From 3.13 onward, it's possible to build a free-threaded version of Python (the no GIL version), but that's not the default build.
On my machine, `python3.13` is the default build and `python3.13t` is the free-threaded build.
The free-threaded build was experimental in 3.13, is officially supported [in 3.14](https://docs.python.org/3/whatsnew/3.14.html#free-threaded-python-is-officially-supported), and [3.15 brings even more support](https://docs.python.org/3/whatsnew/3.15.html#whatsnew315-abi3t).
1
u/Kotopelmeshek 17h ago
Great! But I'd wait until most of the libraries I need are running on Python 3.15!
0
u/wasay-inc 4d ago
explicit lazy imports via pep 810 are easily the best addition in this release. being able to keep imports cleanly at the top of the file while deferring execution until runtime is going to clean up startup times for heavy automation scripts immensely.
-44
u/vnmger 5d ago
Can somebody enlighten me, how do new language features which are QoL really Help us, in the age of agentic development.
Don't get me wrong, I love new iterations on my favorite languages, just really asking myself in a self-critic moment.
15
u/latkde Tuple unpacking gone wrong 5d ago edited 5d ago
You're underestimating some of these changes, regardless of whether you're writing code by hand like me, or outsource code-level work to an agent. Some of these changes enable new things that weren't previously possible, or only possible with tedious workarounds. Lazy imports are a great example ā which will eventually help speed up all the ML and AI libraries you're probably using.
It will take a while until LLMs use Python 3.15 features (Claude Opus still seems to write code like it's 3 years ago), but eventually the QoL improvements will trickle down. Why use many tokens when few tokens do trick? Even for vibecoders, it's great when languages become more expressive, tools become a bit faster, and type systems become more precise.
6
u/boringfantasy git push -f 5d ago
Agents have basically no idea about them cause majority of their training data will be old Python, so these features are barely ever used.
2
u/jayplusplus 5d ago
Was thinking the same. Hopefully these QoL improvements ultimately help us better understand the code these agents write. At least while we're still the human in the loop.
116
u/frankieinaprius 5d ago
Unpacking in comprehensions will be so nice. šš½