One more year has passed, and Python 3.13 is out! It was released on October 7th, 2024. I'm sure you're as excited as we are to see what it brings. This article will take a look into the novelties that will make your Python development even better:
- Improved interactive interpreter - e.g., multiline editing with history preservation
- No-GIL, free-threaded Python - utilize all available processing power by running threads in parallel on all available CPUs
- Improved error messages - more helpful error messages for common mistakes
- Improved typing -
typing.ReadOnly
to mark read-only attributes inside typed dicts - Improved warnings - new
warnings.deprecated()
decorator to emit warnings at runtime and type checking if a deprecated object/function/overload is used - Improved argparse - a new
deprecated
argument was added to support deprecation of command-line options, positional arguments, and subcommands
Contents
Installing Python 3.13
If you have Docker, you can quickly spin up a Python 3.13 shell to play around with the examples in this article like so:
$ docker run -it --rm python:3.13
Not using Docker? We recommend installing Python 3.13 with pyenv:
$ pyenv install 3.13.0
You can learn more about managing Python with pyenv from the Modern Python Environments - dependency and workspace management article.
Improved Interactive Interpreter
Have you ever tried to exit the REPL with exit
, but it didn't work? Well, now you can! The new REPL now supports calling REPL-specific commands just by name -- no need to call them as functions anymore.
Python < 3.13:
>>> exit()
Python >= 3.13:
>>> exit
Obviously, the old way of calling them as functions still works.
Another nugget of goodness is multiline editing with history preservation. You can now edit multiline statements in the REPL without losing the history of the previous lines. This is a great improvement for debugging and testing code snippets.
Using history with Python < 3.13
Defining function:
>>> def foo():
... print('Hello')
Going through history with up arrow:
>>> print('Hello')
>>> def foo():
Using history with Python >= 3.13
Defining function:
>>> def foo():
... print('Hello')
Going through history with up arrow:
>>> def foo():
... print('Hello')
Another thing worth mentioning is that prompts and tracebacks are now colored by default. That makes everything much more readable.
More info:
No-GIL, Free-threaded Python
As already described in Python 3.12: What's New, the Global Interpreter Lock (GIL) is a mutex (or lock) that protects access to Python objects, preventing multiple threads from executing Python bytecodes at once. This lock is necessary mainly because CPython's memory management is not thread-safe. The GIL is controversial because it prevents multithreaded CPython programs from taking full advantage of multiprocessor systems in certain situations.
Well not anymore! CPython finally comes with an option for execution with the GIL disabled. For now, this is only experimental. To make use of it, you'll need to install a special version of Python. Anyhow, with that, you can utilize all available processing power by running threads in parallel on all available CPUs.
More info:
Improved Error Messages
As with the last three Python releases, Python 3.13 comes with improved error messages. This time, they've improved error messages for some of the common mistakes. Also, the interpreter now uses color when showing errors inside the terminal. This will make debugging easier and more efficient.
From now on, you'll get very useful error message if your module name will collide with a standard library module:
# math.py
import math
print(math.sqrt(25))
Python < 3.13:
$ python math.py
AttributeError: partially initialized module 'math' has no attribute 'sqrt'
(most likely due to a circular import)
Python >= 3.13:
$ python math.py
AttributeError: module 'math' has no attribute 'sqrt'
(consider renaming 'math.py' since it has the same name as the standard library
module named 'math' and the import system gives it precedence)
Another example is when you provide an incorrect keyword argument to a function -- the interpreter will now try to suggest the correct one:
# hello.py
def say_hello(*, full_name):
return f"Hello, {full_name}!"
print(say_hello(fullname="Python"))
Python < 3.13:
$ python hello.py
TypeError: say_hello() got an unexpected keyword argument 'fullname'
Python >= 3.13:
$ python hello.py
TypeError: say_hello() got an unexpected keyword argument 'fullname'.
Did you mean 'full_name'?
Review the official docs for more info.
Also, check out the following resources if you're curious about the error message improvements to the previous three versions of Python:
- Python 3.10: Clearer Error Messages
- Python 3.11: Better Error Messages
- Python 3.12: Better Error Messages
Improved Typing
Yet again, some typing improvements have been made in Python 3.13. Let's look at the most interesting one: The new typing.ReadOnly
type. It can be used to mark read-only attributes inside typed dicts. This is a great way to ensure that some key is not modified by mistake.
# song.py
from typing import ReadOnly, TypedDict
class Song(TypedDict):
name: ReadOnly[str]
band: ReadOnly[str]
number_of_plays: int
def count_plays(s: Song) -> None:
s["number_of_plays"] += 1 # OK
s["name"] = "New Name" # Error
$ python -m mypy song.py
song.py:11: error: ReadOnly TypedDict key "name"
TypedDict is mutated [typeddict-readonly-mutated]
More info:
Improved Warnings
Python 3.13 comes with the new warnings.deprecated()
decorator. It can be used to emit warnings at runtime if a deprecated object/function/overload is used. This is a great way to inform users that some part of the code is deprecated and should be replaced with a new one.
# integration.py
from warnings import deprecated
@deprecated("Use NewIntegration instead")
class LegacyIntegration:
pass
class NewIntegration:
pass
LegacyIntegration()
$ python integration.py
integration.py:12: DeprecationWarning: Use NewIntegration instead
LegacyIntegration()
Type checkers will also complain when an object is decorated with the warnings.deprecated()
decorator.
More info:
Improved argparse
The last improvement we'll cover in this article is the new deprecated
argument in argparse
. It can be used to support deprecation of command-line options, positional arguments, and subcommands. This is a great way to inform users that some part of the CLI is deprecated and should be replaced with a new one.
# cli.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--old", help="Old option", deprecated=True, required=False)
parser.add_argument("--new", help="New option", required=False)
args = parser.parse_args()
$ python cli.py --old foo
cli.py: warning: option '--old' is deprecated
Review the official docs for more info.
Conclusion
Like with Python 3.12, 3.13 doesn't seem like a huge release; however, many of the improvements are very welcome. For beginners, the improved error messages will make debugging easier. More advanced users should be sure to make use of the new warnings.deprecated()
decorator. Python 3.14 is coming next year. Until then, happy coding!