This is a quick reference showing how to use type annotations for various common types in Python.
Many of the type annotations shown below are technically redundant, since mypy can usually infer the type of a variable from its value. See the type inference documentation for more details.
# Declare the type of a variableage: int = 1# You don't need to initialize a variable to annotate ita: int # OK (no value at runtime until assigned)# Useful in conditional brancheschild: boolif age < 18: child = Trueelse: child = False
# For collections, the type of the collection item is in bracketsx: list[int] = [1, 2, 3]x: set[int] = {6, 7}# For mappings, we need the types of both keys and valuesx: dict[str, float] = {"field": 2.0}# For tuples of fixed size, specify the types of all elementsx: tuple[int, str, float] = (3, "yes", 7.5)# For tuples of variable size, use one type and ellipsisx: tuple[int, ...] = (1, 2, 3)
On Python 3.8 and earlier, import capitalized versions from typing: List, Set, Dict, Tuple.
from typing import Union, Optional# Use | when something could be one of a few types (Python 3.10+)x: list[int | str] = [3, 5, "test", "fun"]# On earlier versions, use Unionx: list[Union[int, str]] = [3, 5, "test", "fun"]# Use X | None for a value that could be None (Python 3.10+)x: str | None = "something" if some_condition() else None# On Python 3.9 and earlier, use Optional[X] (equivalent to X | None)x: Optional[str] = "something" if some_condition() else None
x: str | None = "hello"if x is not None: # Mypy understands x won't be None here because of the if-statement print(x.upper())# If you know a value can never be None due to some logic that mypy doesn't understandassert x is not Noneprint(x.upper())
# Functions that accept BankAccount also accept any subclass of BankAccountclass AuditedBankAccount(BankAccount): # You can optionally declare instance variables in the class body audit_log: list[str] def __init__(self, account_name: str, initial_balance: int = 0) -> None: super().__init__(account_name, initial_balance) self.audit_log: list[str] = [] def deposit(self, amount: int) -> None: self.audit_log.append(f"Deposited {amount}") self.balance += amountaudited = AuditedBankAccount("Bob", 300)transfer(audited, account, 100) # Type checks!
class A: # This will allow assignment to any A.x, if x is the same type as "value" def __setattr__(self, name: str, value: int) -> None: ... # This will allow access to any A.x, if x is compatible with the return type def __getattr__(self, name: str) -> int: ...a = A()a.foo = 42 # Worksa.bar = 'Ex-parrot' # error: Incompatible type
Use these abstract types when you need duck typing:
from collections.abc import Mapping, MutableMapping, Sequence, Iterable# Use Iterable for generic iterables (anything usable in "for")def f(ints: Iterable[int]) -> list[str]: return [str(x) for x in ints]f(range(1, 3))# Use Sequence where a sequence (supporting "len" and "__getitem__") is requireddef f(seq: Sequence[int]) -> int: return seq[0] + seq[-1]
from typing import reveal_type# To find out what type mypy infers for an expression,# wrap it in reveal_type(). Mypy will print an error message# with the type; remove it again before running the code.reveal_type(1) # Revealed type is "builtins.int"
# If you initialize a variable with an empty container or None,# you may need to provide an explicit type annotationx: list[str] = []x: str | None = None
from typing import Any# Use Any if you don't know the type of something or it's too# dynamic to write a type forx: Any = mystery_function()# Mypy will let you do anything with x!x.whatever() * x["you"] + x("want") - any(x) and all(x) is super # No errors
Use Any sparingly - it defeats the purpose of type checking. Prefer more specific types when possible.
# Use a "type: ignore" comment to suppress errors on a given linex = confusing_function() # type: ignore # confusing_function won't return None here because ...# You can also specify which error code to ignorex = foo() # type: ignore[arg-type]
from typing import cast# cast() lets you override the inferred type of an expression# It's only for mypy - there's no runtime check!a = [4]b = cast(list[int], a) # Passes finec = cast(list[str], a) # Passes fine despite being a lie (no runtime check)reveal_type(c) # Revealed type is "builtins.list[builtins.str]"print(c) # Still prints [4]
cast() performs no runtime checking. Use it only when you know more about the type than mypy does.
from typing import TYPE_CHECKING# Use TYPE_CHECKING for code that mypy can see but won't execute at runtimeif TYPE_CHECKING: import expensive_moduledef process(x: expensive_module.Thing) -> None: # mypy sees this ... # but expensive_module isn't actually imported at runtime
from __future__ import annotations# With this import, you can reference classes before they're defineddef f(foo: A) -> int: # OK ...class A: # You can also reference a class inside its own definition @classmethod def create(cls) -> A: ...
import asyncio# A coroutine is typed like a normal functionasync def countdown(tag: str, count: int) -> str: while count > 0: print(f'T-minus {count} ({tag})') await asyncio.sleep(0.1) count -= 1 return "Blastoff!"# The return type is the type of the value yielded by the coroutine,# not wrapped in Coroutine or Awaitable
# Use a tuple for functions that return multiple valuesdef divide(a: int, b: int) -> tuple[int, int]: return a // b, a % bquotient, remainder = divide(10, 3)