Python 3.9 Is Here! 7 Exciting New Features to Supercharge Your Code

It‘s that time of year again – a new major Python release is upon us! On October 5th, 2020, the Python Software Foundation unleashed Python 3.9 into the world. This highly-anticipated release brings a slew of exciting new features and improvements to our favorite programming language. Whether you‘re a seasoned Python pro or just starting your Python journey, Python 3.9 has something for everyone.

Two key themes of this release are making our code more efficient and more readable. Python 3.9 introduces several new operators and methods that allow us to solve common problems in fewer lines of code, while also making our intentions clearer to others (and let‘s be honest, to ourselves when we revisit code months later). There are also a number of improvements that will help catch subtle bugs, standardize behaviors across implementations, and support the latest technologies.

In this article, we‘ll take a deep dive into 7 of the most exciting features that Python 3.9 brings to the table. We‘ll explore practical use cases and see how these features can supercharge our Python code. Let‘s get started!

1. New Dictionary Merge & Update Operators

Dictionaries are a fundamental data structure in Python and are used in almost every program to store key-value pairs. Python 3.9 introduces some really nice enhancements to working with dictionaries that will both save us some keystrokes and make our code more readable.

First up is the new dictionary merge operator |. This allows us to merge two dictionaries together into a new dictionary, like so:

x = {‘a‘: 1, ‘b‘: 2}
y = {‘c‘: 3, ‘d‘: 4} 
z = x | y
print(z)  # {‘a‘: 1, ‘b‘: 2, ‘c‘: 3, ‘d‘: 4}

Previously, merging dictionaries required a bit more ceremony:

z = {**x, **y}

Or you might have used the dict() constructor:

z = dict(x, **y)

Neither of these options were particularly intuitive or easy to understand at a glance. The new | merge operator makes it crystal clear what‘s going on – we‘re merging x and y into a new dictionary z.

What about when we want to merge dictionaries and update an existing dictionary? Python 3.9 has us covered with the new update operator |=:

x = {‘a‘: 1, ‘b‘: 2}
y = {‘b‘: 3, ‘c‘: 4}
x |= y
print(x)  # {‘a‘: 1, ‘b‘: 3, ‘c‘: 4}

This is equivalent to:

x.update(y) 

Except it allows us to do the update inline as part of an expression or assignment statement. Again, very handy and makes our code more concise and expressive.

One thing to note about both | and |= is that if there are keys in common between the dictionaries, the keys in the second dictionary "win" and overwrite the values from the first dictionary. In the example above, y[‘b‘] overwrites x[‘b‘] when merging.

2. Powerful New Type Hinting Capabilities

Python is a dynamically-typed language, which means we don‘t have to declare the types of our variables, function parameters, and return values up front as we would in a statically-typed language like Java. However, in recent versions Python has been adding more and more support for optional "type hinting".

Type hints don‘t change the runtime behavior of our code, but they do allow type checkers and IDEs to help us catch certain bugs earlier. They also serve as a form of documentation, making it clear to other developers (and again, to our future selves) what types different parts of our code are expecting.

Python 3.9 adds even more powerful type hinting capabilities. Some key enhancements include:

  • Support for generic types in standard collections. We can now write type hints like list[int] to indicate a list of integers, or dict[str, float] for a dictionary mapping strings to floats.

  • More precise types for mappings and sequences. Instead of using the generic Mapping and Sequence types, we can use the new collections.abc types like MutableMapping and SupportsIndex to more narrowly type hint our code.

  • Annotated and TypeGuard helper types for more advanced hinting use cases.

  • Flexible function and variable annotation syntax, allowing type hints to be used in more places and in combination with other things like decorators.

Here‘s an example of some of what‘s possible with the new type hinting features:

from collections.abc import MutableMapping
from typing import NewType, TypeVar, TypedDict

UserId = NewType(‘UserId‘, int)
T = TypeVar(‘T‘)

class User(TypedDict):
    id: UserId
    name: str
    email: str

def get_user_email(user_lookup: MutableMapping[UserId, User], user_id: UserId) -> str:
    return user_lookup[user_id][‘email‘]

In this code, we‘re defining a UserId type to represent our custom User ID type, a generic T type variable, a User TypedDict to define the structure of a User dictionary, and a get_user_email function that takes a MutableMapping from UserId to User and a UserId, and returns the email as a string.

This is just scratching the surface of what we can do with Python 3.9‘s new type hinting features. To learn more, check out the mypy documentation on new type system features in Python 3.9.

3. Handy New String Methods

Python‘s string handling is one of its greatest strengths, and 3.9 adds a couple handy new methods for cleaning up strings.

First, str.removeprefix(prefix) will remove the specified prefix string from the start of a string, if it‘s present:

>>> ‘Hello, world!‘.removeprefix(‘Hello, ‘)
‘world!‘

If the prefix isn‘t found, the original string is returned unchanged:

>>> ‘Hello, world!‘.removeprefix(‘Hi, ‘)
‘Hello, world!‘ 

Similarly, str.removesuffix(suffix) will remove a suffix from the end of a string:

>>> ‘image.png‘.removesuffix(‘.png‘)
‘image‘

These methods nicely complement the existing str.lstrip() and str.rstrip() methods for stripping whitespace or specified characters from the start or end of a string. Having dedicated removeprefix() and removesuffix() methods eliminates the need for awkward slicing syntax when we want to remove a specific prefix or suffix.

4. Useful Additions to the Math Module

Python‘s math module received several useful additions in 3.9 as well:

  • math.gcd() for calculating the greatest common divisor between two numbers has been generalized to accept any number of arguments, not just two. So we can now write things like:
>>> math.gcd(64, 48, 80) 
16

To find the largest positive integer that divides all of the given integers.

  • A new math.lcm() function has been added to calculate the least common multiple of a set of integers:
>>> math.lcm(4, 6, 12)
12  
  • math.nextafter(x, y) returns the next representable floating point number after x in the direction of y. This is useful for exploring the gaps between floating point numbers.

  • math.ulp(x) returns the "unit in the last place" value for a floating point number x. This is the smallest possible change that can be made to x and is useful for determining acceptable error tolerances in floating point comparisons and algorithms.

5. Consistent Import Error Behavior

One pain point in previous versions of Python was that different import mechanisms could raise different exceptions when an import failed. Specifically, relative imports beyond the top-level package would raise a ValueError, while the same import using the import() function would raise an ImportError instead.

In Python 3.9, this has been cleaned up so that all failed relative imports beyond the top-level will consistently raise ImportError. This makes error handling around imports more straightforward and is just one more small papercut fixed that will make the development experience in Python a little smoother.

6. Reproducible Random Bytes Generation

The random module has a new randbytes() function that provides a more convenient way to generate random bytes, especially for use cases where reproducibility is important.

Prior to 3.9, generating random bytes typically involved using one of these functions:

  • os.getrandom() which is not reproducible
  • os.urandom() which is also not reproducible
  • secrets.token_bytes() which is secure but not reproducible
random.randbytes(n)

Will return a bytes object containing n random bytes. The nice thing about randbytes() compared to the other options is that it uses the same pseudorandom number generator as the rest of the random module. So by setting a seed value, we can generate random bytes in a reproducible way:

import random  

random.seed(1234)
print(random.randbytes(8))
# b‘\xb8\x1f\x1a\x18\xb5\xe0 \xb6‘

random.seed(1234)  
print(random.randbytes(8))
# b‘\xb8\x1f\x1a\x18\xb5\xe0 \xb6‘

This can be useful when we need random bytes for testing purposes or pseudo-random data generation and want it to be repeatable.

The secrets module should still be preferred for any cryptography or security use cases. But for general "random bytes" needs, randbytes() is now the most convenient option.

7. Built-in IANA Time Zone Support

Python 3.9 introduces a new zoneinfo module that brings support for the IANA time zone database into the standard library. IANA is the organization that maintains the standard "tz database" that defines time zone names and daylight saving transition rules used by most systems, so having built-in support for it is a big deal.

Previously, to do proper time zone handling in Python we had to rely on third-party libraries like pytz. Now, we can do things like convert timestamps between time zones using only the standard library:

from zoneinfo import ZoneInfo
from datetime import datetime

dt = datetime(2020, 10, 31, 12, tzinfo=ZoneInfo(‘America/Los_Angeles‘))
print(dt)
# 2020-10-31 12:00:00-07:00

print(dt.astimezone(ZoneInfo(‘Europe/Madrid‘)))  
# 2020-10-31 20:00:00+01:00

Having the IANA database available out of the box will make it easier to write timezone-aware applications and reduces the burden of having to pull in and install additional dependencies.

The zoneinfo module doesn‘t completely replace third-party libraries like pytz or pendulum, as those offer additional features like timezone localization and more convenient API for common tasks. But for basic timezone representation and arithmetic, zoneinfo covers the most fundamental use cases.

Other Assorted Goodies

In addition to the big headlining features we covered above, Python 3.9 also brought a whole host of other improvements, both user-facing and under the hood. A few more things worth mentioning:

  • A new high-performance PEG parser has been added as an option for CPython‘s implementation. This should speed up Python‘s startup time and reduce memory usage.

  • The new graphlib module contains tools for working with graph data structures.

  • Python‘s asyncio coroutines and multithreading capabilities continue to mature, with new features added in both areas to support concurrent programming.

  • The http module now supports more HTTP status codes like 418 "I‘m a Teapot" and 425 "Too Early".

  • Various deprecated modules, functions, and other cruft have been removed, allowing the Python core team to focus their efforts on maintaining the features that provide the most value.

Even for the features and modules you might not personally use in your day-to-day work, it‘s encouraging to see the Python community and core development team consistently working hard to evolve the language to be more efficient, expressive, and powerful with each release.

Get Out There and Experiment!

We‘ve taken a look at 7 of the most exciting features in Python 3.9, but this is really just a tiny slice of everything that‘s new. To get the full scoop, check out the official release notes, which go into way more detail on these features and cover many other changes we didn‘t have space for here.

No matter what area of Python development you work in – web, data science, systems automation, etc. – you‘re sure to find something in Python 3.9 to geek out about. The only way to really appreciate the impact of the new features is to get hands-on with them.

So spin up a new virtual environment, pip install the latest Python version, and start playing around! See which of the new toys you like best and share your thoughts with the community. As Pythonistas, we‘re all on this journey together, continually finding ways to improve our craft by adopting new language features and best practices as they evolve.

Happy coding!

How useful was this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.

Similar Posts