Python Typing

typing and typing_extensions modules support the type annotation system.

typing_extensions is the experimental namespace; typing is the mature namespace. Any classes or functions that have migrated from the former to the latter will be documented only as being a member of typing.

The type annotation system has evolved rapidly with many breaking changes. Anything that has been moved out of typing (and typing_extensions) will be documented elsewhere, with a note about the migration.


Primitives and Collections

AbstractSet

Do not use typing.AbstractSet[...] in Python 3.9+; instead use collections.abc.Set[...]. See here for more details.

Any

Any satisfies all type checking, which can be useful for making things 'just work'.

AnyStr

If a function can take either a normal string or a byte string (b""), annotate the argument with AnyStr.

This will satisfy type checkers while still catching issues relating to the combination of these incompatible types

def concat(a: AnyStr, b: AnyStr) -> AnyStr:
    return a + b

# pass
concat("foo", "bar")

# pass
concat(b"foo", b"bar")

# fail
concat("foo", b"bar")

AsyncContextManager

Do not use typing.AsyncContextManager[...] in Python 3.9+; instead use contextlib.AbstractAsyncContextManager[...]. See here for more details.

AsyncGenerator

Do not use typing.AsyncGenerator[...] in Python 3.9+; instead use collections.abc.AsyncGenerator[...]. See here for more details.

AsyncIterable

Do not use typing.AsyncIterable[...] in Python 3.9+; instead use collections.abc.AsyncIterable[...]. See here for more details.

AsyncIterator

Do not use typing.AsyncIterator[...] in Python 3.9+; instead use collections.abc.AsyncIterator[...]. See here for more details.

Awaitable

Do not use typing.Awaitable[...] in Python 3.9+; instead use collections.abc.Awaitable[...]. See here for more details.

BinaryIO

A subclass of typing.IO specific to byte streams plus type annotations for a few byte stream specific methods. Leaving the second point aside, equivalent to IO[bytes].

ByteString

Do not use typing.ByteString[...] in Python 3.9+; instead use collections.abc.ByteString[...]. See here for more details.

Callable

Do not use typing.Callable[...] in Python 3.9+; instead use collections.abc.Callable[...]. See here for more details.

ChainMap

Do not use typing.ChainMap[...] in Python 3.9+; instead use collections.chainmap[...]. See here for more details.

Collection

Do not use typing.Collection[...] in Python 3.9+; instead use collections.abc.Collection[...]. See here for more details.

Container

Do not use typing.Container[...] in Python 3.9+; instead use collections.abc.Container[...]. See here for more details.

ContextManager

Do not use typing.ContextManager[...] in Python 3.9+; instead use contextlib.AbstractContextManager[...]. See here for more details.

Coroutine

Do not use typing.Coroutine[...] in Python 3.9+; instead use collections.abc.Coroutine[...]. See here for more details.

Counter

Do not use typing.Counter[...] in Python 3.9+; instead use collections.Counter[...]. See here for more details.

DefaultDict

Do not use typing.DefaultDict[...] in Python 3.9+; instead use collections.defaultdict[...]. See here for more details.

Deque

Do not use typing.Deque[...] in Python 3.9+; instead use collections.deque[...]. See here for more details.

Dict

Do not use typing.Dict[...] in Python 3.9+; instead use the built-in dict[...]. See here for more details.

FrozenSet

Do not use typing.FrozenSet[...] in Python 3.9+; instead use the built-in frozenset[...]. See here for more details.

Generator

Do not use typing.Generator[...] in Python 3.9+; instead use collections.abc.Generator[...]. See here for more details.

Hashable

An alias for collections.abc.Hashable. See here for more details.

ItemsView

Do not use typing.ItemsView[...] in Python 3.9+; instead use collections.abc.ItemsView[...]. See here for more details.

Iterable

Do not use typing.Iterable[...] in Python 3.9+; instead use collections.abc.Iterable[...]. See here for more details.

Iterator

Do not use typing.Iterator[...] in Python 3.9+; instead use collections.abc.Iterator[...]. See here for more details.

IO

A byte or string stream, such as what is returned by open(). Can be type constrained to one or the other with IO[bytes] or IO[str].

KeysView

Do not use typing.KeysView[...] in Python 3.9+; instead use collections.abc.KeysView[...]. See here for more details.

List

Do not use typing.List[...] in Python 3.9+; instead use the built-in list[...]. See here for more details.

Literal

To annotate an argument or return value that must be one of a set of literal values, use Literal.

from typing import Literal

def true() -> Literal[True]:
    return True

def open_helper(file: str, mode: Literal['r', 'rb', 'w', 'wb']) -> None:
    pass

LiteralString

To annotate a string argument or return value that must one of...

...use LiteralString. As an example, consider SQL templating functions.

from typing import LiteralString

def run_query(sql: LiteralString) -> None
    pass

def caller(arbitrary_string: str, literal_string: LiteralString) -> None:
    # pass, because is a literal string
    run_query("SELECT * FROM students")

    # pass, because is a value annotated as a LiteralString
    run_query(literal_string)

    # pass, because is a combination of a literal string and a value annotated as a LiteralString
    run_query("SELECT * FROM " + literal_string)

    run_query(arbitrary_string)  # type checker error
    run_query(  # type checker error
        f"SELECT * FROM students WHERE name = {arbitrary_string}"
    )

This adds a degree of safety by way of the type checker.

The following str methods all preserve LiteralString annotation.

Mapping

Do not use typing.Mapping[...] in Python 3.9+; instead use collections.abc.Mapping[...]. See here for more details.

MappingView

Do not use typing.MappingView[...] in Python 3.9+; instead use collections.abc.MappingView[...]. See here for more details.

Match

Do not use typing.Match[...] in Python 3.9+; instead use re.Match[...]. See here for more details.

MutableMapping

Do not use typing.MutableMapping[...] in Python 3.9+; instead use collections.abc.MutableMapping[...]. See here for more details.

MutableSequence

Do not use typing.MutableSequence[...] in Python 3.9+; instead use collections.abc.MutableSequence[...]. See here for more details.

MutableSet

Do not use typing.MutableSet[...] in Python 3.9+; instead use collections.abc.MutableSet[...]. See here for more details.

NamedTuple

Named tuples are created like:

from collection import namedtuple

Employee = namedtuple('Employee', ['name', 'id'])

To create a named tuple with type annotations, the equivalent code is:

from typing import NamedTuple

class User(NamedTuple):
    name: str
    uid: int

It is also possible to insert methods and docstrings into named tuples created in this manner.

To create a named tuple with generic types, try:

from typing import NamedTuple, Generic, TypeVar

T = TypeVar('T')

class User(NamedTuple, Generic[T]):
    own_group: T
    all_groups: list[T]

Optional

To annotate an argument that can also be None, use Optional.

from typing import Optional

def int_or_none(arg: Optional[int]) -> None:
    if arg is not None:
        # type checkers understand type guards; `arg` is checked as an `int` here
        arg += 1

OrderedDict

Do not use typing.OrderedDict[...] in Python 3.9+; instead use collections.ordereddict[...]. See here for more details.

Pattern

Do not use typing.Pattern[...] in Python 3.9+; instead use re.Pattern[...]. See here for more details.

Reversible

Do not use typing.Reversible[...] in Python 3.9+; instead use collections.abc.Reversible[...]. See here for more details.

Set

Do not use typing.Set[...] in Python 3.9+; instead use the built-in set[...]. See here for more details.

Sequence

Do not use typing.Sequence[...] in Python 3.9+; instead use collections.abc.Sequence[...]. See here for more details.

Sized

An alias for collections.abc.Sized. See here for more details.

TextIO

A subclass of typing.TextIO specific to string streams plus type annotations for a few string stream specific methods. Leaving the second point aside, equivalent to IO[str].

The added method annotations include:

Tuple

Do not use typing.Tuple[...] in Python 3.9+; instead use the built-in tuple[...]. See here for more details.

TypedDict

To create a dictionary with type annotations, try:

from typing import TypedDict

class Point(TypedDict):
    x: int
    y: int
    label: str

# alternatively written as:
#Point = TypedDict('Point', {'x': int, 'y': int, 'label': str})

# pass
a: Point = {'x': 1, 'y': 2, 'label': 'good'}

# fail
b: Point = {'z': 3, 'label': 'bad'}

By default, all attributes in a TypedDict are required in order to pass a type checker. Annotate optional attributes with NotRequired.

from typing import TypedDict, NotRequired

class Point(TypedDict):
    x: int
    y: int
    NotRequired[label]: str

The inverse behavior can also be acheived.

from typing import TypedDict, Required

class Point(TypedDict, total=False):
    x: Required[int]
    y: Required[int]
    label: str

# alternatively written as:
#Point = TypedDict('Point', {'x': Required[int], 'y': Required[int], 'label': str}, total=False)

TypedDict definitions can inherit from other TypedDict definitions.

class 3DPoint(Point):
    z: int

To create a TypedDict with generic types, try:

from typing import TypedDict, Generic, TypeVar

T = TypeVar('T')

class User(TypedDict, Generic[T]):
    own_group: T
    all_groups: list[T]

Union

To annotate an argument that can be multiple types, use Union.

from typing import Union

def int_or_str(arg: Union[int, str]) -> None:
    if isinstance(arg, int):
        # type checkers understand type guards; `arg` is checked as an `int` here
        arg += 1
    else:
        # type checkers also infer that `arg` is a `str` here
        arg += "1"

If the argument can be either a type or None, consider Optional.

See also the union operator (|).

ValuesView

Do not use typing.ValuesView[...] in Python 3.9+; instead use collections.abc.ValuesView[...]. See here for more details.


Annotations for Type Hints

The following annotations are mostly useful for visual hints in IDEs.

Annotated

from typing import Annotated

SmallInt = Annotated[int, ValueRange(0, 9)]


Annotations for Annotations

The following annotations are mostly useful for supporting annotations.

Self

For class methods that return an instance of the class, use Self. This is superior to using forward references because subclasses will automatically have the correct annotation.

from typing import Self

class Foo:
    def return_self(self) -> Self:
        return self

TypeGuard

Functions like isinstance act as type guards. Type checkers understand that subsequent conditional logic will only be called if the value is of a known, constrained type.

Custom type guards can be written and annotated with TypeGuard[T]. If the function returns True, then the value is of type T.

from typing import TypeGuard

def is_str_list(val: list[object]) -> TypeGuard[list[str]]:
    return all(isinstance(x, str) for x in val)


Functions for Annotations

The following functions are mostly useful for supporting annotations.

Assert_Type

Assert that a value is of a constrained type.

from typing import assert_type

def int_or_str(arg: int | str) -> None:
    if isinstance(arg, int):
        return
    assert_type(arg, str)

Cast

Tells the type checker that a value is of a constrained type. At runtime, immediately returns the value.

from typing import cast, TypeVar

T = TypeVar('T')

def to_int(arg: T) -> int:
    return case(arg, int)

Clear_Overloads

Remove @overload function definitions in runtime.

Get_Args

Inspect special types.

from typing import get_args, Union

assert get_args(Union[int, str]) == (int, str)

Get_Origin

Inspect special types.

from typing import get_origin, Union

assert get_origin(Union[int, str]) is Union

Get_Type_Hints

Inspect type hints for a function, method, module or class object. This performs some mutation of annotations, such as the evaluation of forward references.

Get_Overloads

Inspect the overwritten @overload function definitions in runtime.

Is_TypedDict

Checks if a type is a TypedDict.

@No_Type_Check

Decorate objects with @no_type_check to tell type checkers that the object should not be checked. With classes it is applied to all methods, attributes, and classes defined within, but not to subclasses or superclasses.

@No_Type_Check_Decorator

Decorate a decorator function with @no_type_check.

@Overload

To annotate a function with complex internal logic that returns different types based on the argument types, use the @overload decorator.

@overload
def process(response: None) -> None:
    ...
@overload
def process(response: int) -> tuple[int, str]:
    ...
@overload
def process(response: bytes) -> str:
    ...
def process(response):

At runtime the preceding definitions of process() are overwritten by the final one, erasing them from the namespace.

Reveal_Type

When a type checker reaches a call to reveal_type(), it produces diagnostic information.

x: int = 0
reveal_type(x)     # "builtins.int"

y = reveal_type(1) # "builtins.int"

At runtime it prints the value's type (to stderr) and returns it unchanged.

@Type_Check_Only

Decorate objects with @type_check_only to indicate that an object is only available to type checkers, and will be unavailable at runtime.


Annotations for Logic Checks

The following annotations are mostly useful for causing a type checker to identify issues with program logic. They may be necessary to satisfy the type checker in edge cases.

ClassVar

If there is a class attribute that should never be set on an instance, annotate with ClassVar.

from typing import ClassVar

class Quadruped:
    feet: ClassVar[int] = 4

cat = Quadruped("cat")

# fail
cat.feet = 3

Final

If there is a constant that should never be changed, annotate with Final.

from typing import Final

MAX_CONN: Final[int] = 1

# fail
MAX_CONN += 1

Never

If there is a function that should never be called, annotate the argument with Never.

from typing import Never

def never_call_me(arg: Never) -> None:
    pass

# fail
def do_it_anyway(arg: int | str) -> None:
    never_call_me(arg)

# pass: arg is either int or str so the default case is never reached
def int_or_str(arg: int | str) -> None:
    match arg:
        case int():
            pass
        case str():
            pass
        case _:
            never_call_me(arg)

NoReturn

Do not use NoReturn in Python 3.11+; instead use Never.


Functions for Logic Checks

The following functions are mostly useful for causing a type checker to identify issues with program logic.

Assert_Never

An assertion that, if ever reached, raises an error (both in type checkers and in runtime).

from typing import assert_never

def int_or_str(arg: int | str) -> None:
    match arg:
        case int():
            pass
        case str():
            pass
        case _ as unreachable:
            assert_never(unreachable)

@Final

If there is a method that should never be overwritten, decorate it with @final.

class Base:
    @final
    def done(self) -> None:
        pass

# fail
class Sub(Base):
    def done(self) -> None:
        pass

If there is a class that should never be subclassed, decorate it with @final.

@final
class Person:
    ...

# fail
class Android(Person):
    ...


Custom Types

To create a custom type, use NewType. Type checkers will treat the custom type as a subclass of the original type, while rejecting passed arguments of the original type.

from typing import NewType

UserId = NewType('UserId', int)

# passes
user_a = get_user_name(UserId(42351))

# fails
user_b = get_user_name(-1)

Given the above example, at runtime, UserId returns a callable that immediately returns the original value. This leads to three properties:

  1. UserId(value) has minimal runtime overhead

  2. an instance of UserId cannot be returned at runtime; UserId(value) immediately evaluates to value

  3. UserId is not subclassable (except by chaining NewType: ProUserId = NewType('ProUserId', UserId))


Protocols

Protocol is a base class for protocols.

from typing import Protocol

class SomeProtocol(Protocol):
    def meth(self) -> int:
        ...

class A:
    def meth(self) -> int:
        return 0

class B:
    def meth(self) -> int:
        return 1

def func(c: SomeProtocol) -> int:
    return c.meth()

# pass
func(A())

# pass
func(B())

Protocol classes can be generic as well.

from typing import TypeVar, Protocol

T = TypeVar('T')

class GenericProtocol(Protocol[T]):
    def meth(self) -> T:
        ...

@Runtime_Checkable

If a protocol is decorated with @runtime_checkable, then it can be used at runtime for isinstance and issubclass checking.

from typing import Protocol, runtime_checkable

@runtime_checkable
class Closable(Protocol):
    def close(self):
        ...

assert isinstance(open('/some/file'), Closable)

Note that method type signatures are not checked; only the presence of the required methods. This effectively implements duck type checking.

Pre-defined Protocols

The following protocols are defined within the typing namespace, with the @runtime_checkable decorator.


Generic Types

To annotate a generic type, use TypeVar.

from collections.abc import Sequence
from typing import TypeVar

T = TypeVar('T')

def first(l: Sequence[T]) -> T:
    return l[0]

There are additional type constructs for variadic generics.

For example, to constrain the type of the first or last value in a tuple while leaving others alone, use TypeVarTuple.

from typing import TypeVar, TypeVarTuple

T = TypeVar('T')
Ts = TypeVarTuple('Ts')

def move_first_element_to_last(tup: tuple[T, *Ts]) -> tuple[*Ts, T]:
    return (*tup[1:], tup[0])

TypeVarTuple is only valid when used with the expansion operator.

from typing import TypeVar, TypeVarTuple

T = TypeVar('T')
Ts = TypeVarTuple('Ts')

# fail
x: tuple[Ts]

# pass
x: tuple[*Ts]

In the above context, *Ts would be equivalent to using Unpack[Ts]. Unpack exists because the expansion operator formerly could not be used in this way. Do not use Unpack in Python 3.11+.

Abstract Generic Class

Base classes are constructed like:

from typing import TypeVar, Generic

KT = TypeVar('KT')
VT = TypeVar('VT')

class Mapping(Generic[KT, VT]):
    def __getitem__(self, key: KT) -> VT:
        ...

X = TypeVar('X')
Y = TypeVar('Y')

def lookup_name(mapping: Mapping[X, Y], key: X, default: Y) -> Y:
    try:
        return mapping[key]
    except KeyError:
        return default


Metaprogramming

@Dataclass_Transform

Decorate objects with @dataclass_transform to tell type checkers that the object performs runtime magic.

With decorator functions:

from typing import TypeVar, dataclass_transform

T = TypeVar("T")

@dataclass_transform()
def create_model(cls: type[T]) -> type[T]:
    return cls

@create_model
class CustomerModel:
    id: int
    name: str

With base classes:

from typing import dataclass_transform

@dataclass_transform()
class ModelBase: ...

class CustomerModel(ModelBase):
    id: int
    name: str

With metaclasses:

from typing import dataclass_transform

@dataclass_transform()
class ModelMeta(type): ...

class ModelBase(metaclass=ModelMeta): ...

class CustomerModel(ModelBase):
    id: int
    name: str

Concatenate and ParamSpec

To annotate a decorator that inserts an argument into a function's argument tuple, use Concatenate and ParamSpec.

from collections.abc import Callable
from threading import Lock
from typing import Concatenate, ParamSpec, TypeVar

P = ParamSpec('P')
R = TypeVar('R')

my_lock = Lock() # a thread-safe lock that will be shared by `@with_lock`

def with_lock(f: Callable[Concatenate[Lock, P], R]) -> Callable[P, R]:
    def inner(*args: P.args, **kwargs: P.kwargs) -> R:
        return f(my_lock, *args, **kwargs)
    return inner

@with_lock
def sum_threadsafe(lock: Lock, numbers: list[float]) -> float:
    with lock:
        return sum(numbers)

# pass type checker AND thread-safe at runtime
sum_threadsafe([1.1, 2.2, 3.3])


See also

Python typing module documentation

Python typing guide and reference materials


CategoryRicottone

Python/Typing (last edited 2023-04-20 14:07:06 by DominicRicottone)