What Is New in Python 3.9
Python 3.9 shipped on October 5, 2020. The headline additions are dictionary merge operators, built-in generic types for type hints, two new string methods, a brand-new PEG parser, and the zoneinfo module for IANA timezone support -- all without any third-party dependencies.
| Category | Change | PEP / Reference |
|---|---|---|
| New Features | Dictionary merge (|) and update (|=) operators |
PEP 584 |
| New Features | Built-in collections as generic types in type hints (list[str], dict[str, int]) |
PEP 585 |
| New Features | str.removeprefix() and str.removesuffix() |
PEP 616 |
| New Features | Relaxed decorator grammar -- any valid expression allowed | PEP 614 |
| New Modules | zoneinfo -- IANA timezone database support |
PEP 615 |
| New Modules | graphlib -- topological sort for graphs |
-- |
| Interpreter | New PEG-based parser replaces the old LL(1) parser | PEP 617 |
| Performance | Vectorcall applied to range, tuple, set, frozenset, list, dict |
PEP 590 |
| Performance | GC no longer blocks on resurrected objects | -- |
| Standard Library | os.pidfd_open() for race-free process management |
PEP 573 |
| Standard Library | Flexible function/variable annotations via Annotated |
PEP 593 |
| Deprecated | Many stdlib modules deprecated in favor of modern alternatives (e.g., distutils) |
-- |
| Platform | First version to default to 64-bit installer on Windows; dropped Windows 7 support | -- |
What Are the New Language Features in Python 3.9?
Dictionary Merge and Update Operators (PEP 584)
The | operator merges two dicts into a new one, and |= updates a dict in place. Before 3.9, the cleanest way to merge was {**a, **b}, which is less readable and harder to chain.
defaults = {"color": "blue", "size": "M"}
overrides = {"size": "L", "weight": "heavy"}
merged = defaults | overrides
# {"color": "blue", "size": "L", "weight": "heavy"}
defaults |= overrides # in-place update
In practice, this is most useful in config-layering patterns -- think environment overrides on top of defaults without a helper function.
Built-in Generic Types for Type Hints (PEP 585)
You can now annotate directly with list[str], dict[str, int], tuple[int, ...], and so on -- no more importing List, Dict, Tuple from typing. This works at runtime too, not just in type checkers.
# Before 3.9
from typing import List, Dict
def process(items: List[str]) -> Dict[str, int]: ...
# Python 3.9+
def process(items: list[str]) -> dict[str, int]: ...
New String Methods: removeprefix and removesuffix (PEP 616)
Two long-requested convenience methods land in 3.9. Both return a new string -- they never raise, and if the prefix/suffix doesn't match, the original string is returned unchanged.
"TestHook".removeprefix("Test") # "Hook"
"MiscTests".removesuffix("Tests") # "Misc"
"Misfile".removesuffix("Tests") # "Misfile" -- no change
This replaces the fragile s[len(prefix):] pattern and the incorrect s.lstrip(prefix) pattern that strips characters, not substrings.
Relaxed Decorator Grammar (PEP 614)
Decorator expressions are no longer restricted to dotted names and simple calls. Any valid Python expression is now accepted. This unlocks patterns like @buttons[0].clicked.connect in GUI code and computed decorators in meta-programming frameworks.
What New Standard Library Modules Shipped in Python 3.9?
zoneinfo -- IANA Timezone Support (PEP 615)
The zoneinfo module adds first-class support for the IANA time zone database, the same database used by your OS. Before this, timezone-aware datetime work required pytz or dateutil.
from zoneinfo import ZoneInfo
from datetime import datetime
dt = datetime(2024, 6, 1, 12, 0, tzinfo=ZoneInfo("America/New_York"))
print(dt) # 2024-06-01 12:00:00-04:00
On systems without the system timezone data, install the tzdata package from PyPI as a fallback.
graphlib -- Topological Sorting
The new graphlib.TopologicalSorter handles dependency ordering for DAGs. It supports both static pre-computation and incremental node-by-node processing, which is useful for build systems and task schedulers.
from graphlib import TopologicalSorter
graph = {"D": {"B", "C"}, "C": {"A"}, "B": {"A"}}
ts = TopologicalSorter(graph)
print(list(ts.static_order())) # ['A', 'C', 'B', 'D']
CPython Internals: The New PEG Parser (PEP 617)
CPython replaced its LL(1) parser with a PEG (Parsing Expression Grammar) parser. This change is invisible to application code but significant for CPython maintainers -- PEG grammars are unambiguous by construction and far easier to extend without grammar hacks.
The old parser had known limitations that forced workarounds in CPython's grammar. The PEG parser unlocks cleaner syntax additions in future releases (structural pattern matching in 3.10 was one early beneficiary). You can revert to the old parser with -X oldparser in 3.9, but that flag was removed in 3.10.
Performance Improvements and Optimizations
- Many built-in types (
range,tuple,set,frozenset,list,dict) now use the vectorcall protocol, cutting call overhead for hot paths. - Garbage collection no longer blocks on objects that are resurrected (i.e., acquired a new reference inside
__del__). - Several C extension modules migrated to multiphase initialization (PEP 489) and stable ABI (PEP 384), improving embedding compatibility.
os.path.join()andos.path.normpath()are faster on common cases.str()for subclasses is faster when__str__is not overridden.
Deprecations and Removals in Python 3.9
- Deprecated aliases in
typingfor built-in generic types (typing.List,typing.Dict, etc.) -- they will be removed in a future version. - The
distutilspackage is deprecated and scheduled for removal in 3.12. - The old LL(1) parser is deprecated (and will be removed in 3.10).
- Several modules deprecated:
aifc,audioop,cgi,crypt,imghdr,mailcap,msilib,nis,nntplib,ossaudiodev,pipes,sndhdr,spwd,sunau,telnetlib,uu,xdrlib.
FAQ
Can I use list[str] and dict[str, int] at runtime in Python 3.9, or only in type checkers?
Both. PEP 585 made built-in collection types real generic aliases at runtime, so list[str] evaluates to a types.GenericAlias object and works in isinstance()-like checks via __class_getitem__. No import from typing needed.
Is the new PEG parser backward compatible with existing Python code?
Yes. The parser change is entirely internal -- existing syntax is parsed identically. The only observable difference is error messages, which are sometimes more precise. The -X oldparser flag exists in 3.9 as an escape hatch but was removed in 3.10.
What is the difference between zoneinfo and pytz?
zoneinfo uses IANA timezone names directly and loads zone data from the OS (or the tzdata package). Unlike pytz, it does not require calling localize() -- you pass the ZoneInfo object directly to tzinfo. DST transitions are handled correctly without the workaround pattern that pytz requires.
Does the dict merge operator | work with dict subclasses?
It depends on the subclass. The left operand's type determines the result type. If you merge a defaultdict | dict, you get a defaultdict. If a custom subclass does not override __or__, Python falls back to the base dict implementation and you may lose subclass-specific behavior.
When should I use removesuffix instead of a manual slice?
Always prefer removesuffix -- the manual slice s[:-len(suffix)] breaks when suffix is an empty string (it removes everything). removesuffix("") returns the original string unchanged, which is the correct and expected behavior.