Python, the dynamic programming language created by Guido van Rossum, has become one of the most popular tools for developers thanks to its simplicity, flexibility, and power. At Neogeo, we use it to develop the backend of our applications, write administration scripts, and do data science.
To mark the release of Python 3.13 on October 7, we will review the key changes introduced in Python versions 3.9 (the latest version still maintained) through 3.13.
These new versions bring significant improvements in terms of performance, with JIT code optimization and the removal of the GIL, a real bottleneck for multi-threaded applications. They also offer syntax enhancements such as the “matching” expression.
Other updates: Python’s standard library has also been updated. Type handling in Python has also been improved, with the introduction of dynamic types. We will cover these changes in a dedicated article.
In this article, we will guide you through the main changes introduced in Python 3.9 to 3.13, and provide examples to help you leverage these new features and improve your applications.
Performance
In Python 3.9, the performance of certain built-in structures such as dict, list, and set was improved. Access to Python variables from C modules was also accelerated (we will not cover this topic in this article).
Python 3.11 was heavily optimized; the documentation mentions an average 25% performance gain, with improvements ranging from 10% to 60% depending on the type of workload.
Work on the GIL has paid off in versions 3.12 and 3.13. The GIL, Global Interpreter Lock, is a Python mechanism that prevents simultaneous access to a variable by multiple threads. While very simple for developers, it prevents the creation of efficient multi-threaded programs in Python. At a time when CPUs and cores are multiplying, it is a burden for our little language. Developers have worked to remove this mechanism, but they must proceed in stages. In version 3.12, Python can now use one GIL per interpreter, and it can even be disabled from the command line in 3.13.
It is worth noting that asyncio was optimized in version 3.12, with performance gains of up to 75%. This Python version also benefits from a new API for less intrusive monitoring (with less impact on performance).
In addition, version 3.13 also includes an experimental JIT compiler, which can be enabled from the command line. The documentation indicates that the performance gains are modest. It is reasonable to assume that future Python versions will include a more sophisticated and higher-performing JIT compiler.
Syntax enhancements
Python 3.9 introduced dictionary union operators | and |=. While not revolutionary—you can still use the update function to do the same thing—it is simpler to write.

Perhaps less impactful, it is now possible to use Python expressions as a decorator. This allows you to call functions or request an item from a list when using the decorator:

In addition, strings have two new methods to remove suffixes and prefixes:

Pattern matching finally arrived in Python in version 3.10. It is the equivalent of the well-known switch-case found in C or Java. I am using the example from the documentation:

Structural Pattern Matching is very powerful in Python because you can unpack parameters, assign variables, add conditions, etc. I recommend reading PEP636 for more details.
The other syntax change in Python 3.10 is the ability to use parentheses in a with clause, notably to allow multi-line usage:

Python 3.11 introduced exception groups. It is now possible to raise multiple exceptions at the same time. This can be useful for entering multiple except sections to fix several issues. Here is the very clear example from the documentation that shows how to use it (thanks again to the documentation for the example):

Another addition to Exceptions that I find very useful is the add_note() function, which allows you to add details when catching/re-raising an exception:

If you look closely at Python error messages (in the traceback in particular), you will now see that the interpreter indicates more precisely where the error is located in the line, and not only which line contains the error.
Version 3.12 introduced a small syntax change that will simplify the use of f-strings: it is now possible to use the same string delimiters in the expression context as in the parent string:

However, there is no syntax change in version 3.13. There are still improvements to error messages, and the interpreter will return colorized error messages.
Standard library
Fewer and fewer changes are being made to the standard library. In Python 3.9, the main addition is a zoneinfo module for handling time zones:

There is also a new topological sorting module, graphlib.
In the Python 3.10 standard library, the zip function has a new strict parameter that checks that the sizes of the two input iterators are identical.
Python 3.11 introduced a new tomllib module for reading the TOML file format (but not writing it). The API is similar to that of the json module:

Conclusion
Python is clearly becoming an increasingly stable language; there is little left to add to its syntax to improve code readability. The same can be said of its standard function library.
Major work to improve performance and clean up the standard library has been undertaken and is gradually bearing fruit.
We look forward to taking advantage of the advances in this new 3.13 release in our projects.
What is changing the most in Python right now is clearly typing. But that will be for a future article!
Authors: Mathilde Pommier and Sébastien Da Rocha