What changed in Python 3.10 and which of those changes matter for you?
I’ve spent this week playing with Python 3.10. I’ve primarily been working on solutions to Python Morsels exercises that embrace new Python 3.10 features. I’d like to share what I’ve found.
Easier troubleshooting with improved error messages
The biggest Python 3.10 improvements by far are all related improved error messages. I make typos all the time. Error messages that help me quickly figure out what’s wrong are really important.
I’ve already grown accustom to the process of deciphering many of Python’s more cryptic error messages. So while improved error messages are great for me, this change is especially big for new Python learners.
When I teach an introduction to Python course, some of the most common errors I help folks debug are:
- Missing colons at the end of a block of code
- Missing indentation or incorrect indentation in a block of code
- Misspelled variable names
- Brackets and braces that were never closed
Python 3.10 makes all of these errors (and more) much clearer for Python learners.
New Python users often forget to put a :
to begin their code blocks.
In Python 3.9 users would see this cryptic error message:
1 2 3 4 5 |
|
Python 3.10 makes this much clearer:
1 2 3 4 5 |
|
Indentation errors are clearer too (that after 'if' statement on line 4
is new):
1 2 3 4 5 |
|
And incorrect variable and attribute names now show a suggestion:
1 2 3 4 5 |
|
I’m really excited about that one because I make typos in variable names pretty much daily.
The error message shown for unclosed brackets, braces, and parentheses is also much more helpful.
Python used to show us the next line of code after an unclosed brace:
1 2 3 4 5 |
|
Now it instead points to the opening brace that was left unclosed:
1 2 3 4 5 |
|
You can find more details on these improved error messages in the better error messages section of the “What’s new in Python 3.10” documentation.
While Python 3.10 does include other changes (read on if you’re interested), these improved error messages are the one 3.10 improvement that all Python users will notice.
IDLE is more visually consistent
Here’s another feature that affects new Python users: the look of IDLE improved a bit.
IDLE now uses spaces for indentation instead of tabs (unlike the built-in REPL) and the familiar ...
in front of REPL continuation lines is now present in IDLE within a sidebar.
Before IDLE looked like this:
Now IDLE looks like this:
Looks a lot more like the Python REPL on the command-prompt, right?
Length-checking for the zip function
There’s a Python Morsels exercise called strict_zip
.
It’s now become a “re-implement this already built-in functionality” exercise.
Still useful for the sake of learning how zip
is implemented, but no longer useful day-to-day code.
Why isn’t it useful?
Because zip
now accepts a strict
argument!
So if you’re working with iterables that might be different lengths but shouldn’t be, passing strict=True
is now recommended when using zip.
Structural pattern matching
The big Python 3.10 feature everyone is talking about is structural pattern matching. This feature is very powerful but probably not very relevant for most Python users.
One important note about this feature: match
and case
are still allowable variable names so all your existing code should keep working (they’re soft keywords).
Matching the shape and contents of an iterable
You could look at the new match
/case
statement as like tuple unpacking with a lot more than just length-checking.
Compare this snippet of code from a Django template tag:
1 2 3 4 |
|
To the same snippet refactored to use structural pattern matching:
1 2 3 4 5 |
|
Notice that the second approach allows us to describe both the number of variables we’re unpacking our data into and the names to unpack into (just like tuple unpacking) while also matching the second and third values against the strings for
and as
.
If those strings don’t show up in the expected positions, we raise an appropriate exception.
Structural pattern matching is really handy for implementing simple parsers, like Django’s template language. I’m looking forward to seeing Django’s refactored template code in 2025 (after Python 3.9 support ends).
Complex type checking
Structural pattern matching also excels at type checking. Strong type checking is usually discouraged in Python, but it does come crop up from time to time.
The most common place I see isinstance
checks is in operator overloading dunder methods (__eq__
, __lt__
, __add__
, __sub__
, etc).
I’ve already upgraded some Python Morsels solutions to compare and contrast match
-case
and isinstance
and I’m finding it more verbose in some cases but also occasionally somewhat clearer.
For example this code snippet (again from Django):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Can be replaced by this code snippet instead:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Note how much shorter each condition is.
That case
syntax definitely takes some getting used to, but I do find it a bit easier to read in long isinstance
chains like this.
Bisecting with a key
Python’s bisect
module is really handy for quickly finding an item within a sorted list.
For me, the bisect
module is mostly a reminder of how infrequently I need to care about the binary search algorithms I learned in Computer Science classes.
But for those times you do need to find an item in a sorted list, bisect
is great.
As of Python 3.10, all the binary search helpers in the bisect
module now accept a key
argument.
So you can now quickly search within a case insensitively-sorted list of strings for the string you’re looking for.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Doing a search that involved a key
function was surprisingly tricky before Python 3.10.
Slots for data classes
Have a data class (especially a frozen one) and want to make it more memory-efficient?
You can add a __slots__
attribute but you’ll need to type all the field names out yourself.
1 2 3 4 5 6 7 |
|
In Python 3.10 you can now use slots=True
instead:
1 2 3 4 5 6 |
|
This feature was actually included in the original dataclass implementation but removed before Python 3.7’s release (Guido suggested including it in a later Python version if users expressed interest and we did).
Creating a dataclass with __slots__
added manually won’t allow for default field values, which is why slots=True
is so handy.
There’s a very smaller quirk with slots=True
though: super
calls break when slots=True
is used because this causes a new class object to be created which breaks the magic of super.
But unless you’re using calling super().__setattr__
in the __post_init__
method of a frozen dataclass instead of calling object.__setattr__
, this quirk likely won’t affect you.
Type annotation improvements
If you use type annotations, type unions are even easier now using the |
operator (in addition to typing.Union
).
Other big additions in type annotation land include parameter specification variables, type aliases, and user-defined type guards.
I still don’t use type annotations often, but these features are a pretty big deal for Python devs who do.
Also if you’re introspecting annotations, calling the inspect.get_annotations
function is recommended over accessing __annotations__
directly or calling the typing.get_type_hints
function.
Checking for default file encoding issues
You can also now ask Python to emit warnings when you fail to specify an explicit file encoding (this is very relevant when writing cross operating system compatible code).
Just run Python with -X warn_default_encoding
and you’ll see a loud error message if you’re not specifying encodings everywhere you open files up:
1 2 3 4 |
|
Plus lots more
The changes above are the main ones I’ve found useful when updating Python Morsels exercises over the last week. There are many more changes in Python 3.10 though.
Here are a few more things I looked into, and plan to play with later:
- keyword-only dataclass fields
- The
fileinput.input
(handy for handling standard input or a file) function now accepts anencoding
argument importlib
deprecations: some of my dynamic module importing code was using features that are now deprecated in Python 3.10 (you’ll notice obvious deprecation warnings if your code needs updating too)- Dictionary views have a
mapping
attribute now: if you’re making your own dictionary-like objects, you should probably add amapping
attribute to yourkeys
/values
/items
views as well (this will definitely crop up in Python Morsels exercises in the future) - When using multiple context managers in a single
with
block, parentheses can now be used to wrap them onto the next line (this was actually added in Python 3.9 but unofficially) - The names of standard library modules and built-in modules are now included in
sys.stdlib_module_names
andsys.builtin_module_names
: I’ve occasionally needed to distinguish between third party and standard library modules dynamically and this makes that a lot easier sys.orig_argv
includes the full list of command-line arguments (including the Python interpreter and all arguments passed to it) which could be useful when inspecting how your Python process was launched or when re-launching your Python process with the same arguments
Summary
Structural pattern matching is great and the various other syntax, standard library, and builtins improvements are lovely too. But the biggest improvement by far are the new error messages.
And you know what’s even better news than the new errors in Python 3.10? Python 3.11 will include even better error messages!
Get practice with Python 3.10
Want to try out Python 3.10? Try out the Python 3.10 exercise path on Python Morsels. It’s free for Python Morsels subscribers and $17 for non-subscribers.
Python Morsels currently includes 170 Python exercises and 80 Python screencasts with a new short screencast/article hybrid added each week. This service is all about hands-on skill building (we learn and grow through doing, not just reading/watching).
I’d love for you to come learn Python (3.10) with me! 💖