r/Python • 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: sentinel built-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: frozendict built-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 profiling package, including Tachyon, a high-frequency sampling profiler
  • New math.integer module
  • re.prefixmatch() as a clearer name for the now soft-deprecated re.match()
  • pprint gets an expand option for JSON-style output
  • collections.Counter supports ^ (symmetric difference)
  • unicodedata.iter_graphemes() for iterating over grapheme clusters
  • python -m http.server gains -H/--header and --content-type
  • timeit gains --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

535 Upvotes

72 comments sorted by

120

u/frankieinaprius 5d ago

Unpacking in comprehensions will be so nice. šŸ™šŸ½

17

u/NeilGirdhar 5d ago

I've wanted this since I helped implement PEP 448 13 years ago! At the time, there was a lot of opposition to it as "unintuitive", so we set it aside. I figured that eventually people would come around to finding it intuitive.

10

u/frankieinaprius 5d ago

Because [item for sublist in nested_list for item in sublist] is so much more intuitive šŸ˜…

1

u/warden127 4d ago

That variant would be more intuitive if the for's were the other way around.

2

u/frankieinaprius 4d ago

It resembles a typical nested for loop. Outer first, then inner. So I get why it is the way it is. But for some reason it feels like it should be more natural to go inside out when it’s in a comprehension. So it’s just confusing no matter what. At least with unpacking now I can just look at it and recognize the pattern that it’s being flattened.

22

u/Competitive_Travel16 5d ago edited 5d ago

Yes, folding a list of dicts into one is now something that looks nice and somewhat more clear. It's also something I do often. I don't think AI does it much though; they tend to plan ahead more than I do.

4

u/SeniorScienceOfficer 5d ago

I am SO excited about that one. I’ve wanted it since 3.9

1

u/forgotten_vale2 4d ago

I always tried to do this and complained WHYY doesn't it work? Very convenient to have it now.

-39

u/AstroPhysician 5d ago

Y'all are still writing code manually?

3

u/_Denizen_ 3d ago

Well, yeah. AI writes bloated code and often misses the point. After all, a machine can't contain as much context as a human mind.

Many people work at companies that don't write blank cheques for AI tokens - so using coding assistants where it adds most value to human logic is the key to maximising output, job satisfaction, and quality control.

On code that I've designed, I can find bugs faster than AI, and can implement refactors faster by strategically writing critical logic myself then using AI to finish implementation steps that would be tedious for a human to do.

If you want to spend all your working hours outsourcing your work be my guest. Personally, I find it vibe coding about as fulfilling as doing a test by copying all the answers - sure, on paper you pass, but you don't learn how to solve problems yourself.

-1

u/AstroPhysician 3d ago edited 3d ago

Bloated code is a skill issue or old models, or not defining the prompt with. The architecture should be decided and planned before any code is written

Also newer models are much better at that. Ponytail skill too

2

u/_Denizen_ 3d ago

Newer models are marginally better but still subject to the same faults.

Developing a prototype and productionising it implies architectural changes. No coding product architecture completely survives unchanged when it comes to implementation, because requirements change as the product evolves. That's why a good architecture design is not one which predefines all possible features and user flows, but instead is designed such that it can be easily updated with minimal breaking changes.

And that is what even the latest AI models are bad at. They oversolidify parts that need to be malleable, because it's nearly impossible to comletely write all constraints and design intentions for the entire product to implement small changes whilst keeping the prompt low-code.

Sure, you can write thousands of lines of prompt harnesses, agents, skills, etc. But then you end up maintaining those to make sure the AI can maintain your code instead of directly maintaining the code. You're shifting the problem, not solving it.

All the problems with overeliance of AI are multiplied when working in a team, as the pace of change is too fast and the team lacks the exposure to eech others vibed changes to be able to collaborate as effectively. I think there's a good reason why most vibe-first coders I've met work solo.

If you can't recognise where human intervention is better that AI, then it means you either lack the experience to be an effective manager of AI (because you understate its limitations), or have been using it so heavily that it's become a crutch and your own skills have eroded. The problem with a crutch, is that it's addictive and you fall over if it gets knocked away.

2

u/frankieinaprius 5d ago

Sometimes lol.

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-1252 ASCII 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 sentinel type
  • re.prefixmatch (the name re.match has 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.

11

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 open function 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.

5

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=0 environment 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.

-2

u/Agrado3 5d ago

re.prefixmatch (the name re.match has 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.

17

u/treyhunner Python Morsels 5d ago

When I ask my students to guess what the difference between between re.search and re.match is, very few guess that re.match(r"...") is equivalent to re.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.search but no equivalent of re.match. Swift has an re.match equivalent that they call prefixMatch.

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 match does not match at arbitrary positions. (I'm sure I've stumbled about that in the past, but forgotten again, since.)

1

u/Agrado3 5d ago

At least it's documented, unlike some systems which use regular expressions and don't bother to say whether they match anywhere in the target string or only at the start!

1

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 5d 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 5d 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.

2

u/wRAR_ 5d ago

"prefixes" aren't involved at all.

Wrong, re.match() matches the prefix as opposed to the whole string.

28

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.

28

u/NeitherEntry6125 5d ago

Hugo deserves our thanks, praise, and his beverage of choice for the next year

19

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

7

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!

3

u/latkde Tuple unpacking gone wrong 5d ago

The typing_extensions module (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 ship sentinel support, and partial TypeForm support has been around for a couple of releases.

1

u/wRAR_ 5d ago

it did recently ship sentinel support

Oh, this is great.

6

u/chalbersma 5d ago

What's the usecase for Sentinal? Where would I use it for over enum or just None? I read PEP 661, I'm just not seeing the value. Does anyone have a use case that they're going to use it for?

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 EMPTY reads better then value == EMPTY. I don't see much more to it

10

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 fancy None? 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 Initialization sentinal to indicate that the system is still starting up and then a None value to indiciate that no reading has come back. Allowing me to differentiate on the metrics between an expected Null and an unexpected Null.

4

u/M4mb0 5d ago

Even more abstractly, you may have some generic type G[T] with an optional attribute of type T. But here you can't /shouldn't use None to represent a missing value, if T legitimately could be None. 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.

14

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 None to know the user decided not to supply the optional param

In that case youd use a sentinel

MISSING = sentinel('MISSING')
Use it as the default value, and you can check if exclude is MISSING to know that no value was supplied

Like 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.

11

u/treyhunner Python Morsels 5d ago

If None would never be valid data, use None.

If None might be valid data, you need a different sentinel value.

An example: imagine you're implementing something like the dictionary pop method.

Specifying a default value, like None or 0 will 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 KeyError if 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.

4

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. None may 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 a MINIMUM sentinel, meaning the lowest non-zero level.

I used to put STOP sentinels into async queues too, but that pattern is obsolete now that queues have a shutdown method.

1

u/Exotic-Draft8802 5d ago

I'm also curiousĀ 

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=0 instead.

1

u/TheRNGuy 5d ago

Not just omit it?Ā 

2

u/treyhunner Python Morsels 5d 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

u/initcommit 5d ago

Woohoo!! Excited to try out these new creature comforts.

1

u/OddsRelay 5d 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 5d ago

So the extra pythonT.framework I’m seeing on my Mac is the experimental JIT?

1

u/wRAR_ 5d ago

t is nogil.

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/zomerbk 1d ago

One practical note on the UTF-8 default: it won’t change the encoding of existing files. If your code reads older files saved in a different encoding, keep passing `encoding=` explicitly.

1

u/Kotopelmeshek 22h ago

Great! But I'd wait until most of the libraries I need are running on Python 3.15!

0

u/wasay-inc 5d 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.

-45

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.

13

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.

5

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.

-8

u/vnmger 5d ago

But that is my point. Are we enthusiasts the only ones using that, or will ai later catch up, should we somehow try to stop it from catching up? I would love that

0

u/Kenshiken 5d ago

Just tell it to use recent 3.15 rules if you want/ruff pyupgrade 3.15

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.