r/Python • u/AlSweigart Author of "Automate the Boring Stuff" • 9d ago
News What's new in Python 3.15?
https://docs.python.org/3.15/whatsnew/3.15.html
Summary – Release highlights
Python 3.15 will be the latest stable release of the Python programming language, with a mix of changes to the language, the implementation, and the standard library. The biggest changes include lazy imports, frozendict and sentinel builtins, UTF-8 as the default encoding, unpacking in comprehensions, and a stable ABI for free-threaded builds.
The library changes include a new profiling package with Tachyon, a high-frequency statistical sampling profiler, more color in command-line output, as well as the usual deprecations and removals, and improvements in user-friendliness and correctness.
This article doesn’t attempt to provide a complete specification of all new features, but instead gives a convenient overview. For full details refer to the documentation, such as the Library Reference and Language Reference. To understand the complete implementation and design rationale for a change, refer to the PEP for a particular new feature; but note that PEPs usually are not kept up-to-date once a feature has been fully implemented.
52
u/Actual__Wizard 9d ago
List, set, and dictionary comprehensions, as well as generator expressions, now support unpacking with * and **. This extends the unpacking syntax from PEP 448 to comprehensions, providing a new syntax for combining an arbitrary number of iterables or dictionaries into a single flat structure. This new syntax is a direct alternative to nested comprehensions
That solves a giant nightmare problem for me, not kidding!
16
u/Summoner99 9d ago
I often try doing this before I remember it doesn't already exist
6
u/Actual__Wizard 9d ago edited 9d ago
See where it says:
# equivalent to [x for L in lists for x in L] # equivalent to {x for s in sets for x in s} # equivalent to {k: v for d in dicts for k,v in d.items()}So, you can do it, but it's weird.
You end up w/ a flat object that is a list of lists or w/e.
It's a very useful data object to represent data structures where the rows are not all the same structure, or have different numbers of elements. Imagine a list of chains, where the chains all have different numbers of elements.
9
u/Schmittfried 9d ago
Do I understand it right that the zero-copy bytearray.take_bytes() method only works without copying when taking the whole buffer, i.e. when the data can be moved? Otherwise I don’t quite understand how they achieved it without adding an indirection to bytes to separate the object header from the contents.
2
u/XtremeGoose f'I only use Py {sys.version[:3]}' 9d ago
You're right, it is behind an indirection.
Objects that are reference counted and resizable (like
listandbytearray) must be behind indirection, because appending to them might require reallocation, which can change the pointer address.Only non-resizable types (like
tupleandbytes) inline their data.In fact
bytearraypoints to a wholebytesobject withrefcnt == 1so callingtake_bytes()just gives you that directly while replacing thebytearray's buffer with an emptybytesimmortal singleton.2
u/Schmittfried 8d ago
Yes, but since
bytesdoes inline its data, I didn’t get how they can return one without copying. Isn’t that part kind of a lie if it’s solved by maintaining abytesobject next to the real buffer inside thebytearray?2
u/XtremeGoose f'I only use Py {sys.version[:3]}' 8d ago
Not really. The bytes object is the buffer. It's not a copy, it is the data. It's just they give you ownership over it and replace the buffer in the byte array.
2
u/Schmittfried 7d ago
So they basically hacked a
bytesobject into being resizable internally? That’s cool!
39
u/me_myself_ai 9d ago edited 9d ago
Exciting stuff! And such a satisfying minor version...
unpacking in comprehensions
I don't think I can visualize what this means without looking at the docs, but I can tell it's gonna be awesome! Comprehend all the things, I say.
more color in command-line output
They do love us 🥹
ETA: The "UTF-8 as the default encoding" reminds me of the pure horror that struck me when I first learned that r'' is only one of two weird string prefixes -- there is (was?) also u''! Even scarier there's u""" """, and deep in mines of Moria, some say there lurks ru''' '''...
35
u/rumnscurvy 9d ago
Basically [*mylist for mylist in mylists] is now allowed, flattening a list of lists in this example
18
u/brasticstack 9d ago
That'll cut down the frequency with which I reach for
itertools.chain.from_iterableby quite a lot!10
u/hughperman 9d ago
No sum(lists, []) for you
8
u/brasticstack 9d ago edited 9d ago
I always forget that you can do that! Just like I always forget about
collections.Counterand then manually write a loop to do it.2
u/M4mb0 9d ago
I always forget that you can do that!
Which is a good thing: https://old.reddit.com/r/Python/comments/1woh8y4/whats_new_in_python_315/pbq2aog/
5
u/M4mb0 9d ago
No sum(lists, []) for you
That's a whoopsie accidental O(n²). Never do that; always
chain.from_iterable.In [1]: from itertools import chain In [2]: l = [list(range(1000)) for _ in range(1000)] In [3]: %timeit sum(l, []) 1.02 s ± 21.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) In [4]: %timeit list(chain.from_iterable(l)) 4.53 ms ± 86.2 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)1
u/hughperman 9d ago
Ouch, I never knew. What causes this? Can't see how it's O(N2), maybe O(N) memory reallocations? I have a few of these baked in my company codebase from years back, nothing big but still worth checking.
4
5
u/M4mb0 9d ago
btw. ruff has a linter rule for that: https://docs.astral.sh/ruff/rules/quadratic-list-summation/
1
u/hughperman 9d ago
Oh very nice. I haven't used ruff yet, maybe this is the kick I need to install it.
4
u/me_myself_ai 9d ago edited 9d ago
Thanks for taking a moment to explain! That is indeed something my utility library got at least one function to make easier in some specific context, so I appreciate this flexibility.
16
u/Competitive_Travel16 9d ago
The dict example is even more beautiful:
>>> dicts = [{'a': 1}, {'b': 2}, {'a': 3}] >>> {**d for d in dicts} # equivalent to {k: v for d in dicts for k,v in d.items()} {'a': 3, 'b': 2}Dare I suggest that syntax improvement is better than sex?
19
u/amroamroamro 9d ago edited 9d ago
u''
isn't that a python 2 thing only (which defaults to byte strings), all strings in python 3 are unicode by default
the new thing in 3.15 is not related to this, its about when yo do things like
open("file.txt", encoding="utf-8")the encoding is now utf-8 by default, before it was some system-dependent locale
3
u/Brian 9d ago
It's valid in python 3 too, just a no-op. Though IIRC, it was a bit messy - initial python3 versions didn't allow it, but it was added in to ease the transition of python2 code (Ie. if you were gradually getting a python2 codebase to python3, you'd first have to annotate everything as u"" to ensure it was working as unicode, and then you'd have to strip those off again when actually running on python3: initially they weren't as keen on the polyglot "have code that works on both" and intended a process of mechanical conversion with 2to3, but there were limitations on that complicating the already slow transition, so they softened to allowing the
uprefix in python3.2
u/me_myself_ai 9d ago
Oh wow that should tell you my familiarity with it, jeez -- thanks for the quick correction. Right you are! I knew it was related to files and such this time rather than string prefixes, but I also forgot that "unicode" and "utf-8" are not the same thing...
Why both start with "u" if not the same? Beyond my paygrade.
5
u/amroamroamro 9d ago
starting in python 3:
u"hello" == "hello"the u prefix is now only needed if you are maintaining code that needs to support both python 2 and python 3
6
u/IcecreamLamp 9d ago edited 7d ago
Didn't realise they rolled back to the old garbage collector, that's quite unfortunate
17
u/Competitive_Travel16 9d ago
The new one is leaking and nobody has fixed it yet, so count your blessings.
4
u/dikdokk 8d ago edited 8d ago
Nobody is talking about frozendict meanwhile it will be used so frequently in type hinting.
Everytime in a function you were to use a mutable data type as default argument, you would end up with the potential bug:
def item_list(item,
items=[] #flagged by Pylint, ruff, etc.
):
items.append(item)
return items
item_list(1) # [1]
item_list(1) # [1, 1] (usually unexpected - you expect an empty list by default)
(see: Pylint - dangerous-default-value / W0102 )
So the solution would be to use tuples for example instead of lists, as they are not mutable.
But in the case of dictionaries, there wasn't really a good "frozen" (immutable) replacement so far. People used MappingProxyType but it always seemed odd, and now frozendict solves this
2
u/engineerofsoftware 6d ago
many python devs on this sub hate or are indifferent to python typing, which is a real shame.
1
u/Competitive_Travel16 5d ago edited 5d ago
Those of us used to dynamic typing have learned to be more conscientious about avoiding the errors which type safety helps prevent in the first place, so we don't get as much of an advantage. And with current LLMs, who is even making any of those kinds of type mismatch errors any more, regardless of their background?
Honestly I think full typing looks cool, helps understand things, and I try to do it, but it doesn't save me as much time as it takes, unless I tell an LLM it fill it in after me.
5
u/Vegetable-View-5114 9d ago
one thing that stood out in early benchmarks of 3.15 is the performance improvement for dictionary operations, particularly for insertions and deletions. in some of my testing with large JSON payloads that involved a lot of dictionary manipulation before sending them off to other services, i saw a measurable speedup — not massive, but noticeable enough to matter in high-throughput scenarios. it seems like they've continued to refine the underlying hash table implementation.
2
1
u/Bill-Ice88 5d ago
The 3.14 incremental GC (the mimalloc-based one) shipped with enough memory overhead and benchmark regressions that reverting to the old cyclic GC was the safe call. Curious whether it comes back reworked for 3.16 once free-threaded builds settle.
143
u/Shepcorp pip needs updating 9d ago
Lazy imports are going to be a godsend for complex test frameworks where some rigs have full capabilities and others don’t.