Modernizing Superseded Typing Features¶
Introduction¶
This guide helps to modernize your code by replacing older typing features with their modern equivalents. Not all features described here are obsolete, but they are superseded by more modern alternatives, which are recommended to use.
These newer features are not available in all Python versions, although
some features are available as backports from the
typing-extensions
package, or require quoting or using from __future__ import annotations
when running on Python 3.13 or below.
Each section states the minimum Python version required to use the
feature, whether it is available in typing-extensions, and whether it is
available using quoting.
Tip
Tools such as pyupgrade, ruff and/or com2ann can automatically perform some of these refactorings for you.
Note
The latest version of typing-extensions is available for all Python versions that have not reached their end of life, but not necessarily for older versions.
Note
from __future__ import annotations is available since Python 3.7
and became unnecessary starting with Python 3.14.
This only has an effect inside type annotations, while quoting is still
required outside. For example:
from __future__ import annotations
from typing_extensions import TypeAlias
def f(x: Foo) -> Foo: ... # the future import is sufficient
Alias: TypeAlias = "Foo" # this forward reference requires quoting
class Foo: pass
Type Comments¶
Alternative available since: Python 3.0, 3.6
Type comments were originally introduced to support type annotations in Python 2 and variable annotations before Python 3.6. While most type checkers still support them, they are considered obsolete, and type checkers are not required to support them.
For example, replace:
x = 3 # type: int
def f(x, y): # type: (int, int) -> int
return x + y
with:
x: int = 3
def f(x: int, y: int) -> int:
return x + y
When using forward references or types only available during type checking,
it’s necessary to either use from __future__ import annotations
(available since Python 3.7) or to quote the type:
def f(x: "Parrot") -> int: ...
class Parrot: ...
When using Python 3.14 and up, quoting forward references is no longer necessary inside type annotations.
typing.Text¶
Alternative available since: Python 3.0
typing.Text was a type alias intended for Python 2 compatibility.
It is equivalent to str and should be replaced with it.
For example, replace:
from typing import Text
def f(x: Text) -> Text: ...
with:
def f(x: str) -> str: ...
typing.TypedDict Legacy Forms¶
Alternative available since: Python 3.6
TypedDict supports two legacy forms for
supporting Python versions that don’t support variable annotations.
Replace these two variants:
from typing import TypedDict
FlyingSaucer = TypedDict("FlyingSaucer", {"x": int, "y": str})
FlyingSaucer = TypedDict("FlyingSaucer", x=int, y=str)
with:
class FlyingSaucer(TypedDict):
x: int
y: str
But the dictionary form is still necessary if the keys are not valid Python identifiers:
Airspeeds = TypedDict("Airspeeds", {"unladen-swallow": int})
Generics in the typing Module¶
Alternative available since: Python 3.0 (quoted), Python 3.9 (unquoted)
Originally, the typing module provided aliases for built-in types that
accepted type parameters. Since Python 3.9, these aliases are no longer
necessary, and can be replaced with the built-in types. For example,
replace:
from typing import Dict, List
def f(x: List[int]) -> Dict[str, int]: ...
with:
def f(x: list[int]) -> dict[str, int]: ...
This affects the following types:
typing.Dict(→dict)typing.List(→list)typing.Set(→set)typing.Tuple(→tuple)
The typing module also provided aliases for certain standard library
types that accepted type parameters. Since Python 3.9, these aliases are no
longer necessary, and can be replaced with the proper types. For example,
replace:
from typing import DefaultDict, Pattern
def f(x: Pattern[str]) -> DefaultDict[str, int]: ...
with:
from collections import defaultdict
from re import Pattern
def f(x: Pattern[str]) -> defaultdict[str, int]: ...
This affects the following types:
typing.AbstractSet(→collections.abc.Set), note the change in nametyping.ByteString(→collections.abc.ByteString), but see typing.ByteStringtyping.ContextManager(→contextlib.AbstractContextManager), note the change in nametyping.AsyncContextManager(→contextlib.AbstractAsyncContextManager), note the change in nametyping.Match(→re.Match)
typing.Union and typing.Optional¶
Alternative available since: Python 3.0 (quoted), Python 3.10 (unquoted)
While Union and Optional are
not considered obsolete, using the | (pipe) operator is often more
readable. Union[X, Y] is equivalent to X | Y, while
Optional[X] is equivalent to X | None.
For example, replace:
from typing import Optional, Union
def f(x: Optional[int]) -> Union[int, str]: ...
with:
def f(x: int | None) -> int | str: ...
typing.NoReturn¶
Alternative available since: Python 3.11, typing-extensions
Python 3.11 introduced typing.Never as an alias to
typing.NoReturn for use in annotations that are not
return types. For example, replace:
from typing import NoReturn
def f(x: int, y: NoReturn) -> None: ...
with:
from typing import Never # or typing_extensions.Never
def f(x: int, y: Never) -> None: ...
But keep NoReturn for return types:
from typing import NoReturn
def f(x: int) -> NoReturn: ...
Type Aliases¶
Alternative available since: Python 3.12 (keyword); Python 3.10, typing-extensions
Originally, type aliases were defined using a simple assignment:
IntList = list[int]
Python 3.12 introduced the type keyword to define type aliases:
type IntList = list[int]
Code supporting older Python versions should use
TypeAlias, introduced in Python 3.10, but also
available in typing-extensions, instead:
from typing import TypeAlias # or typing_extensions.TypeAlias
IntList: TypeAlias = list[int]
User Defined Generics¶
Alternative available since: Python 3.12
Python 3.12 introduced new syntax for defining generic classes. Previously,
generic classes had to derive from typing.Generic (or another
generic class) and defined the type variable using typing.TypeVar.
For example:
from typing import Generic, TypeVar
T = TypeVar("T")
class Brian(Generic[T]): ...
class Reg(int, Generic[T]): ...
Starting with Python 3.12, the type variable doesn’t need to be declared
using TypeVar, and instead of deriving the class from Generic, the
following syntax can be used:
class Brian[T]: ...
class Reg[T](int): ...
typing.ByteString¶
Alternative available since: Python 3.0; Python 3.12, typing-extensions
ByteString was originally intended to be a type
alias for “byte-like” types, i.e. bytes, bytearray, and
memoryview. In practice, this
is seldom exactly what is needed. Use one of these alternatives instead:
Just
bytesis often sufficient, especially when not declaring a public API.For items that accept any type that supports the buffer protocol, use
collections.abc.Buffer(available since Python 3.12) ortyping_extensions.Buffer.Otherwise, use a union of
bytes,bytearray,memoryview, and/or any other types that are accepted.
typing.Hashable and typing.Sized¶
Alternative available since: Python 3.12, typing-extensions
The following abstract base classes from typing were added to
collections.abc in Python 3.12:
Update your imports to use the new locations:
from collections.abc import Hashable, Sized
def f(x: Hashable) -> Sized: ...
typing.TypeGuard¶
Available since: Python 3.13, typing-extensions
TypeIs is an alternative to
TypeGuard that usually has more intuitive
behavior, but has other restrictions. See the documentation for
TypeIs for more information.
Review existing uses of TypeGuard to see if they
should be replaced with TypeIs.
from __future__ import annotations¶
Available since: Python 3.14
Starting with Python 3.14, behavior similar to using
from __future__ import annotations became the default. When running on
Python 3.14 and up, it’s no longer necessary to use the future import to defer
evaluation of annotations. However, quoting is still necessary for types used
outside of annotations and type statements, for example when using
TypeAlias.
Remove unnecessary uses of from __future__ import annotations for code
only supporting Python 3.14 and up.