What's new in Python 3.9?

New `str` methods, better typing support, new modules, and more.

  • By Tushar
  • ·
  • Insights
  • Python
Last updated Aug 10, 2021

The latest Python version has become increasingly common among users since its addition to Ubuntu 21.04 a couple of months ago. That gives developers plenty of reason to start using it in their projects right away. So we think it's fair for you to know what features the newest release has to offer — and you're definitely going to like these!

New string methods str.removeprefix and str.removesuffix

This is going to be a fan favorite feature, as it solves a rather confusing mix-up in Python's old str methods.

Suppose you want to remove the .py extension from a file name, how'd you do that? You'd think you could of course use the str.rstrip function to strip out the extension from the right end of the string, and you'd be right:

>>> file_string = 'my_test_file.py'
>>> file_name = file_string.rstrip('.py')
>>> file_name
'my_test_file'

... except this has a fatal issue. See if you can find the problem with this output:

>>> file_string = 'make_happy.py'
>>> file_name = file_string.rstrip('.py')
>>> file_name
'make_ha'

🤨 why did it delete part of the file name?

Well, it's because rstrip (as well as lstrip and strip) doesn't take a "string", but a set of characters to strip. Therefore, file_string.rstrip('.py') doesn't mean strip out .py, it means to strip out any of these characters: ., p, and y, which is why the ppy at the end of our filename go stripped as well. And exactly this has caused bugs and confusion among many other Pythonistas.

And this is why Python 3.9 adds two new methods: str.removeprefix and str.removesuffix which does treat the given string as a string:

>>> file_string = 'make_happy.py'
>>> file_name = file_string.removesuffix('.py')
>>> file_name
'make_happy'

New dict merge and update syntax

Python dictionaries can already be merged and updated, like so:

>>> alice = {'apples': 1, 'bananas': 2}
>>> bob = {'carrots': 3}
>>> merged = {**alice, **bob}  # Dictionary unpacking added in Python 3.5
>>> merged
{'apples': 1, 'bananas': 2, 'carrots': 3}
>>> alice
{'apples': 1, 'bananas': 2}
>>> alice.update(bob)          # Updates alice in-place with bob's values
>>> alice
{'apples': 1, 'bananas': 2, 'carrots': 3}

Since merging and updation are very commonly used features of dictionaries (just like sets), this new addition simply makes these actions simpler, with the | operator:

>>> alice = {'apples': 1, 'bananas': 2}
>>> bob = {'carrots': 3}
>>> merged = alice | bob    # Merges the two into a new dictionary
>>> merged
{'apples': 1, 'bananas': 2, 'carrots': 3}
>>> alice
{'apples': 1, 'bananas': 2}
>>> alice |= bob            # Updates alice in-place
>>> alice
{'apples': 1, 'bananas': 2, 'carrots': 3}
>>>

Type hinting generics in standard collections

I personally am a huge fan of this addition — and if you use type annotations, you'll be a fan of this too. Starting in Python 3.9, you can start using all built-in collection types: list, dict, set, collections.deque etc. in your type annotations, instead of typing.List, typing.Deque, etc. No more typing imports needed!

# Previously:
from collections import defaultdict
from typing import DefaultDict, List, Set

values: List[int] = []
words: Set[str] = set()
counts: DefaultDict[int, int] = defaultdict(int)
# Starting from Python 3.9:
from collections import defaultdict

values: list[int] = []
words: set[str] = set()
counts: defaultdict[int, int] = defaultdict(int)

More information is available in PEP 585.

Two new modules

Python 3.9 introduces two new modules:

  • zoneinfo which brings support for the IANA time zone database to the standard library. What it means is that Python now has built-in support for things like daylight savings, time shifts in countries throughout the year, etc.
  • graphlib, which has an implementation of the topological sort algorithm. So you now have a built-in method for solving complex graph problems, like sorting dependencies in a build system.

CPython has a new, more powerful parser

CPython now uses a new parser based on PEG, instead of the old LL1 algorithm based parser. What this means, is that there were certain limitations to what kind of syntax could be added to Python, as the older parser wouldn't be able to read the more complex code at all.

An example of this is a multi-line with statement:

# Previously:
with open('file1.txt') as f1, \
        open('file2.txt') as f2:
    print(f1.read())
    print(f2.read())

Normally you would use parentheses around a multiline statement to have a more readable line grouping, and to avoid having to put trailing \-es at the end of your lines.

But the older parser in Python 3.8 did not support this syntax:

# Starting from Python 3.9:
with (
    open('file1.txt') as f1,
    open('file2.txt') as f2,
):
    print(f1.read())
    print(f2.read())

The new and improved PEG parser algorithm will be able to handle much more complex syntax parsing, if needed in the future.

More information available in PEP 617.

Other additions

  • CPython adopts an annual release cycle, i.e. a new minor version of CPython will be released every year.
  • New random.Random.randbytes method to generate random bytes.
  • The __file__ built-in variable is always an absolute path now.
  • sys.stderr is always line buffered starting from Python 3.9.
  • ast module updates:
    • New ast.unparse method to convert an AST back to code.
    • Added indent option to ast.dump() for indentation, similar to json.dumps.
  • PEP 614, relaxed grammar restrictions on decorators.

Summary

This version of Python has brought us many convenient additions, while also setting the groundwork for much bigger features down the line with the updates to the parser system and typing changes. For a more detailed look into the changes in this release, you can check out the docs.

Ship clean and secure code.