Python 3.9

Python 3.9 is out (and has been for a while, I’m late posting this), with new features and changes. The themes for this release have been heavily internal improvements, such as a new more powerful parser, and the usual static typing improvements, along with a several bits new-user facing new syntax. This makes 3.9 a smaller release, but still it has some nice features of note.

Posts in the Python Series2→3 3.7 3.8 3.9 3.10 3.11

Dict merging

This is a small feature, but one that has been looked for by Python newbies and experts alike. You can now use set merging on dicts, as well!

dict_1 = {"a": 1, "b": 2}
dict_2 = {"b": 3, "c": 4}
dict_12 = dict_1 | dict_2

This feature was a little slow in coming partially because there’s a choice to be made; in the example above, does dict_12["b"] contain 2 or 3, a mixture of both, or does the expression throw an error? The answer selected by Python is 3; just like with unpacking dict_12 = {**dict_1, **dict_2}, the final item overrides. This syntax works with dict-like classes too, unlike dict unpacking, which always returns a dict, regardless of whether you start with something else like an OrderedDict.

Generics for type hinting

The standard library classes now can be generic for static typing. So list[int] is now valid at runtime, though it does nothing. You an now write either of the following lines:

xs: list[int] = []
xs = list[int]()

These are identical (though the first line is also valid in Python 3.7+ if you use from __future__ import annotations), and are not checked or enforced at runtime. This avoids one of the reasons to import typing in a simple script for static typing (pipe operators for types in 3.10 eliminates even more).

This is enabled by __class_getitem__, which was added in Python 3.7 to speed up and simplify the typing module and classes.

Also for typing, typing.Annotated was added to provide a way to add non-typing information to a type.

Smaller features

A new parser was implemented, though since the old parser is still optionally available, the language doesn’t officially use any of the new syntax. Though if you try a parenthesised with statement, it will work as long as you use the default new parser.

Decorators now have relaxed syntax; pretty much any Python object can be used directly now, instead of just simple dotted expressions.

New string methods .removeprefix and .removesuffix were added, partially because they are sometimes useful, and partially because new users expect .lstrip/.rstrip to have this behavior.

Also, __file__ now is always absolute in your __main__ module.


Time zones

A new zoneinfo library was dded with timezone information. Having time zone info is great for properly handling times.

Topological sorting

A new module graphlib was added with a single topological sorter. This is useful when you have tasks that depend on other tasks.

    "numpy": set(),
    "matplotlib": {"numpy"},
    "pandas": {"numpy", "matplotlib"},
    "requests": set(),
    "uncertainties": {"numpy"},
    "scipy": {"numpy"},

ts = TopologicalSorter(GRAPH)

You can get an order like you see above, or you can get “ready” ones and then indicate which ones are done, checking to see which ones are ready - perfect for multithreading. See is_active(), get_ready(), and done(value).


  • ast.unparse() added as the reverse of .parse(). Also .dump() can take an indent= value.
  • asyncio received several updates, including .to_thread(), which is handy way to integrate with classic threading.
  • cancel_futures=True option added to concurrent.futures.
  • sys.platlibdir added, usually "lib" or "lib64".
  • Several new math functions: lcm(), nextafter(), and ulp().
  • importlib.metadata.files() added
  • os.unsetenv() available on Windows - making putenv and setenv always available.

Other developer changes

Library developers may need to be aware of the following changes:

  • Several optimizations
  • Some deprecations & removals of previously deprecated things
  • Some new C API, like PyType_*, PyFrame_*, and PyThreadState_* additions, also PyModule_AddType().
  • Fast PyObject_CallNoArgs() and PyObject_CallOneArg() functions.

Final words

This was an unusually small release from a user perspective, but there was a lot of work going on behind the scenes, especially related to the brand new and more powerful parser. This was the first release to officially support Apple Silicon.


This blog post was written well after the release, so the primary source was the docs & experience. Here are a few possibly useful links:

Posts in the Python Series2→3 3.7 3.8 3.9 3.10 3.11

comments powered by Disqus