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
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?
zip now accepts a
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:
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
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
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 (
I’ve already upgraded some Python Morsels solutions to compare and contrast
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.
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
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
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
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
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
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
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
fileinput.input(handy for handling standard input or a file) function now accepts an
importlibdeprecations: 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
mappingattribute now: if you’re making your own dictionary-like objects, you should probably add a
mappingattribute to your
itemsviews as well (this will definitely crop up in Python Morsels exercises in the future)
- When using multiple context managers in a single
withblock, 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.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_argvincludes 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
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! 💖