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. 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.

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. This only has an effect inside type annotations, while quoting is still required outside. For example, this example runs on Python 3.7 and up, although the pipe operator was only introduced in Python 3.10:

from __future__ import annotations
from typing_extensions import TypeAlias

def f(x: int | None) -> int | str: ...  # the future import is sufficient
Alias: TypeAlias = "int | str"  # this requires quoting

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: ...

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:

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.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:

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: ...