Here are the most important updates on our favourite programming language.
Even Better Error Messages
When dealing with highly nested data structures (such like API responses) you might encounter a KeyError
when trying to access undefined dictionary keys.
Before Python 3.11 this has been particularly painful. Consider this code snippet:
users = [
{'id': 1, 'name': 'Bas', 'social': {'twitter': '@bascodes'}},
{'id': 2, 'name': 'John Doe', 'social': {}},
]
def get_twitter(user):
return user['social']['twitter']
get_twitter(users[1])
which will produce the following error message on Python 3.10:
Traceback (most recent call last):
File "/Users/sebst/tmp311/py311.py", line 9, in <module>
get_twitter(users[1])
File "/Users/sebst/tmp311/py311.py", line 7, in get_twitter
return user['social']['twitter']
KeyError: 'twitter'
In this small example, we immediately see that the error is because of the missing twitter account of user 2. The more nested these structures become, the more helpful is the new error message format in Python 3.11:
Traceback (most recent call last):
File "/tmp/py311.py", line 9, in <module>
get_twitter(users[1])
^^^^^^^^^^^^^^^^^^^^^
File "/tmp/py311.py", line 7, in get_twitter
return user['social']['twitter']
~~~~~~~~~~~~~~^^^^^^^^^^^
KeyError: 'twitter'
The error message immediately tells us which part of the dictionary is missing. This will become even more helpful when we have two dictionary accesses in one line.
Typing: Adding the Self Type
PEP 673 introduces the Self
type.
Here is an example from the PEP:
from typing import Self
class Shape:
def set_scale(self, scale: float) -> Self:
self.scale = scale
return self
class Circle(Shape):
def set_radius(self, radius: float) -> Self:
self.radius = radius
return self
Typing: Variadic Generics
PEP 646 discusses the introduction of Variadic Generics in the context of numerical libraries, such as NumPy. In that context, a variable is not only characterized by its type but also by its shape.
Particularly, the introduction of TypeVarTuple
allows using an arbitrary-length type variable like so:
from typing import TypeVar, TypeVarTuple
DType = TypeVar('DType')
Shape = TypeVarTuple('Shape')
class Array(Generic[DType, *Shape]):
def __abs__(self) -> Array[DType, *Shape]: ...
def __add__(self, other: Array[DType, *Shape]) -> Array[DType, *Shape]: ...
TOML
The TOML format (maybe popular by using it in pyproject.toml
files) can now be parsed with a tomllib, which will become part of Python 3.11’s standard library through PEP 680.
import tomllib
with open("pyproject.toml", "rb") as f:
data = tomllib.load(f)
Exception Groups / except*
One important feature of Python 3.11 is discussed in PEP 654.
This PEP introduces ExceptionGroup
s. With them, we’re able to raise multiple exceptions simultaneously like so:
try:
raise ExceptionGroup("Validation Errors", (
ValueError("Input is not a valid user name."),
TypeError("Input is not a valid date."),
KeyError("Could not find associated project.")
))
except* (ValueError, TypeError) as exception_group:
...
except* KeyError as exception_group:
...
AsyncIO Task Groups
The idea of TaskGroup
s is to run nested tasks and continue running them even if one fails. Errors are raised using exception groups, so no error will pass silently.
In fact, the previously mentioned ExceptionGroup
s were needed to implement the TaskGroup
s feature.
For more information on TaskGroups, look at this tweet.
try:
async with asyncio.TaskGroup() as tg:
tg.create_task(coro1())
tg.create_task(coro2())
except* ValueError as e:
pass # Ignore all ValueErrors
Performance Improvements
The release notes of Python 3.11.0a6 claim that there is a performance improvement of approx. 19% in CPython 3.11 vs. CPython 3.10.
For a quick overview have a look at this tweet.