Python 3.13 that didn't make the headlines
Resume
The appearance of long-awaited features from the new REPL in the PDB
A large number of fixes for shutil, so you can finally leave prayers when using it
Several small multithreading improvements
The new annotation syntax allows the use of lists and lambda functions.
Python 3.13 still hasn't gotten significantly faster. Alas.
By tradition
Python 3.13 is a great release, full of various features and improvements, but there are already a ton of articles that go into detail release notes. If you need a good squeeze – have RealPython has a good articlebut I don’t see the point of going through them again in this article.
So we won't talk about the new REPL, no-GIL build, experimental JIT compiler, deprecated stuff, new type system goodies or improved error messages (as always, my favorite).
Instead, I read a short book that they call a changelog and we look at something that many have not talked about, but interested me personally.
Make debugging great again
Despite the spartan ergonomics, I really love pdb and even wrote a good one introduction to it.
But if you've ever tried this:
try:
1 / 0
except ZeroDivisionError as e:
breakpoint()
You know perfectly well what will happen when you try to read e
:
-> breakpoint()
(Pdb) e
*** NameError: name 'e' is not defined
This is infuriating, especially because you usually encounter this in not the best frame of mind.
And it's finally fixed.
Glory to Apophis, Quetzalcoatl and Jormungand!
But that's not all. PDB itself also received some improvements:
All this in itself is a good reason to use 3.13.
A little love for working with the file system
It would seem that a 30-year-old language should have no problems when working with paths and files, but it turns out there is always room for improvement.
Module shutilwhich provides high-level FS operations such as recursive deletion or copying, has undergone the following changes: many–many bugs was fixed (especially related to error handling during recursion), and also added some options (for example, it is now possible to choose how to process symlinks).
I was always wary of using shutil because of such “features”. Recursive immersion was broken in one way or another. With these fixes I will be able to try using it again as it is really comfortable when it works.
Likewise for zipfile.Path – a pathlib-compatible wrapper for zip file traversal, which you most likely didn't know existed since its introduction in version 3.8. And you didn’t know about her for a very simple reason – there was nothing good about her. But version 3.13 gave us a lot of QoL patches for it, significantly improving directory handling, which previously required a lot of gestures. So now it can be used more often.
And finally, herself pathlib received many small optimizations, concerning productivitymany operations under the hood now use strings instead of objects Path
. Serialization should also become faster. I like pathlib, but the bottleneck option is known issueso that's great news. I myself have not measured performance in practice, so I will refrain from commenting for now.
But it's not just about performance – there have also been some nice changes to the API. For example, Path.glob()
And rglob()
now accept path-like objects as patterns, Path.glob()
now returns both files and directories if you add to the end of the pattern **
and also a method was added Path.from_uri()
.
Small wins in concurrency
asyncio.as_completed()
now returns an object that is both asynchronous and a regular iterator. Yes, not something large-scale, but not bad.asyncio.TaskGroup
– a great thing, if you haven’t used it yet, I highly recommend it. And now when calledcreate_task()
for an inactive group the coroutine will be closedpreventingRuntimeWarning
.queue.Queue
Now can be explicitly closed challengeshutdown()
helping to communicate to the rest of the system that it's time to stop feeding it data.Maximum number of workers in a pool was increased above 62just in case you – the same person from the thread on Reddit.
Annotation changes that no one asked for
Remember that annotations weren't originally limited to just types? This was an experiment to test what people would use them for and so they accept arbitrary Python expressions.
But it doesn't seem to be arbitrary enough, since the ability to use lists And lambda functions.
The bug report ticket is quite funny:
The following code causes a
SystemError
during compilation.
class name_2[*name_5, name_3: int]:
(name_3 := name_4)
class name_4[name_5: name_5]((name_4 for name_5 in name_0 if name_3), name_2 if name_3 else name_0):
pass
What are you saying, Frank, what are you saying…
Well, let's see what other ways the developers will come up with to mock this functionality.
Disappointments
Performance improvements are not that greatas expected in this release – as in 3.12, we got only a small improvement in traditional benchmarks for async operations (hopefully this doesn't hurt my optimism for pathlilb). Speeding up Python is much, much more difficult than Guido expected. This could be understood from past failed attempts, so it is not particularly surprising.
Like me mentioned in the last post, there really were incremental GC changes canceledbecause they did not give the expected results.
Moreover, despite the many improvements to import in the changelog, my measurements did not show a significant difference in the startup time of Python on my machine.
Well, on top of all this there are inevitable breakdowns:
If you read the changes carefully Path.glob()
Above, you may notice that the return value can vary significantly and I'm guessing this will break someone's code.
Well, a few other details that some may not like:
Usage support
pathlib
. Objectspath
can't do it anymore used for context management. Not that it would be useful. And in general it was confusing. So it's probably a good change, but You know.Launching new threads and processes using
os.fork()
during interpreter shutdown (for example, from handlersatexit
) no longer supported.Positional arguments
maxsplit
,count
Andflags
Forre.split()
,re.sub()
Andre.subn()
marked as obsolete in favor of using keywords. Not removed yet, just a warning. Seems a bit redundant to me.Likewise for all arguments
sqlite3.connect()
except the first one.Obsolescence undocumented functions
glob.glob0()
Andglob.glob1()
. I hated them because they confused my students, so I'm glad about this change, but still.Files
.pth
with names starting with a dot or having the attributehidden
are now ignored for safety reasons.C API headers have been cleanedso we are waiting for dragons on the other side.
Language development is always a delicate balance between clean code, modern features and lack of technical debt on the one hand, and maintaining enough stability to maintain the productivity of the community on the other.
There will always be people who will say that the language got stuck in the past, and those who will ask why everything was broken again. There's no winning here. Language development is a thankless job.
Various pleasant changes
Let's end on a positive note. I don't have any category for these changes:
python -m venv
adds a .gitignore filewhich automatically ignores the virtual environment.json.dumps()
Withindent
Now will use JSON C encoderwhich will make it much faster. Well parsing errors It's much clearer now because of the trailing comma.You can now add private options V
enum
.atexit()
works better with multiprocessing. Most likely due to the previously mentioned deprecation 🙂Dataclass now calls exec() once per 1 dataclass instead of calling for each added method. This can make the process of creating them up to 20% faster.
A combined addition-multiplication operation has been added
math.fma(x, y, z)
. In case you need to work with polynomials right here, right now.time.sleep()
now creates an auditing event. Someone will get an email about this and I can't wait. No, seriously, you don't want to getsleep(1e9)
in critical projects.Such functions
re
Howre.findall()
,re.split()
,re.search()
Andre.sub()
,performing short repeated comparisons can now be aborted by the,user. There are fewer reasons why Ctrl + C might not work.Quantity parameter in
str.replace()
Now could be a keyword.
What surprised me most when reading the changelog for versions 3.12 and 3.13 was the focus on improving existing functionality. A large number of bug fixes, small API changes, attempts to slightly improve performance, code cleanup and removal of obsolete code. Even the famously bad documentation argparse
got better.
After years of chasing new features with async, typing, the walrus operator and multiple interpreters, I expected nothing more than an increase in this trend from new people on the development team. But instead we got improved error messages, an improved PDB, a more flexible parser, an improved REPL, and so on.
And for me this is a sign that they care.