| .. _cheat-sheet-py3: | |
| Type hints cheat sheet | |
| ====================== | |
| This document is a quick cheat sheet showing how to use type | |
| annotations for various common types in Python. | |
| Variables | |
| ********* | |
| Technically many of the type annotations shown below are redundant, | |
| since mypy can usually infer the type of a variable from its value. | |
| See :ref:`type-inference-and-annotations` for more details. | |
| .. code-block:: python | |
| # This is how you declare the type of a variable | |
| age: int = 1 | |
| # You don't need to initialize a variable to annotate it | |
| a: int # Ok (no value at runtime until assigned) | |
| # Doing so can be useful in conditional branches | |
| child: bool | |
| if age < 18: | |
| child = True | |
| else: | |
| child = False | |
| Useful built-in types | |
| ********************* | |
| .. code-block:: python | |
| # For most types, just use the name of the type in the annotation | |
| # Note that mypy can usually infer the type of a variable from its value, | |
| # so technically these annotations are redundant | |
| x: int = 1 | |
| x: float = 1.0 | |
| x: bool = True | |
| x: str = "test" | |
| x: bytes = b"test" | |
| # For collections on Python 3.9+, the type of the collection item is in brackets | |
| x: list[int] = [1] | |
| x: set[int] = {6, 7} | |
| # For mappings, we need the types of both keys and values | |
| x: dict[str, float] = {"field": 2.0} # Python 3.9+ | |
| # For tuples of fixed size, we specify the types of all the elements | |
| x: tuple[int, str, float] = (3, "yes", 7.5) # Python 3.9+ | |
| # For tuples of variable size, we use one type and ellipsis | |
| x: tuple[int, ...] = (1, 2, 3) # Python 3.9+ | |
| # On Python 3.8 and earlier, the name of the collection type is | |
| # capitalized, and the type is imported from the 'typing' module | |
| from typing import List, Set, Dict, Tuple | |
| x: List[int] = [1] | |
| x: Set[int] = {6, 7} | |
| x: Dict[str, float] = {"field": 2.0} | |
| x: Tuple[int, str, float] = (3, "yes", 7.5) | |
| x: Tuple[int, ...] = (1, 2, 3) | |
| from typing import Union, Optional | |
| # On Python 3.10+, use the | operator when something could be one of a few types | |
| x: list[int | str] = [3, 5, "test", "fun"] # Python 3.10+ | |
| # On earlier versions, use Union | |
| x: list[Union[int, str]] = [3, 5, "test", "fun"] | |
| # Use Optional[X] for a value that could be None | |
| # Optional[X] is the same as X | None or Union[X, None] | |
| x: Optional[str] = "something" if some_condition() else None | |
| 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 | |
| # understand, use an assert | |
| assert x is not None | |
| print(x.upper()) | |
| Functions | |
| ********* | |
| .. code-block:: python | |
| from typing import Callable, Iterator, Union, Optional | |
| # This is how you annotate a function definition | |
| def stringify(num: int) -> str: | |
| return str(num) | |
| # And here's how you specify multiple arguments | |
| def plus(num1: int, num2: int) -> int: | |
| return num1 + num2 | |
| # If a function does not return a value, use None as the return type | |
| # Default value for an argument goes after the type annotation | |
| def show(value: str, excitement: int = 10) -> None: | |
| print(value + "!" * excitement) | |
| # Note that arguments without a type are dynamically typed (treated as Any) | |
| # and that functions without any annotations not checked | |
| def untyped(x): | |
| x.anything() + 1 + "string" # no errors | |
| # This is how you annotate a callable (function) value | |
| x: Callable[[int, float], float] = f | |
| def register(callback: Callable[[str], int]) -> None: ... | |
| # A generator function that yields ints is secretly just a function that | |
| # returns an iterator of ints, so that's how we annotate it | |
| def gen(n: int) -> Iterator[int]: | |
| i = 0 | |
| while i < n: | |
| yield i | |
| i += 1 | |
| # You can of course split a function annotation over multiple lines | |
| def send_email(address: Union[str, list[str]], | |
| sender: str, | |
| cc: Optional[list[str]], | |
| bcc: Optional[list[str]], | |
| subject: str = '', | |
| body: Optional[list[str]] = None | |
| ) -> bool: | |
| ... | |
| # Mypy understands positional-only and keyword-only arguments | |
| # Positional-only arguments can also be marked by using a name starting with | |
| # two underscores | |
| def quux(x: int, /, *, y: int) -> None: | |
| pass | |
| quux(3, y=5) # Ok | |
| quux(3, 5) # error: Too many positional arguments for "quux" | |
| quux(x=3, y=5) # error: Unexpected keyword argument "x" for "quux" | |
| # This says each positional arg and each keyword arg is a "str" | |
| def call(self, *args: str, **kwargs: str) -> str: | |
| reveal_type(args) # Revealed type is "tuple[str, ...]" | |
| reveal_type(kwargs) # Revealed type is "dict[str, str]" | |
| request = make_request(*args, **kwargs) | |
| return self.do_api_query(request) | |
| Classes | |
| ******* | |
| .. code-block:: python | |
| class BankAccount: | |
| # The "__init__" method doesn't return anything, so it gets return | |
| # type "None" just like any other method that doesn't return anything | |
| def __init__(self, account_name: str, initial_balance: int = 0) -> None: | |
| # mypy will infer the correct types for these instance variables | |
| # based on the types of the parameters. | |
| self.account_name = account_name | |
| self.balance = initial_balance | |
| # For instance methods, omit type for "self" | |
| def deposit(self, amount: int) -> None: | |
| self.balance += amount | |
| def withdraw(self, amount: int) -> None: | |
| self.balance -= amount | |
| # User-defined classes are valid as types in annotations | |
| account: BankAccount = BankAccount("Alice", 400) | |
| def transfer(src: BankAccount, dst: BankAccount, amount: int) -> None: | |
| src.withdraw(amount) | |
| dst.deposit(amount) | |
| # Functions that accept BankAccount also accept any subclass of BankAccount! | |
| class 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 += amount | |
| def withdraw(self, amount: int) -> None: | |
| self.audit_log.append(f"Withdrew {amount}") | |
| self.balance -= amount | |
| audited = AuditedBankAccount("Bob", 300) | |
| transfer(audited, account, 100) # type checks! | |
| # You can use the ClassVar annotation to declare a class variable | |
| class Car: | |
| seats: ClassVar[int] = 4 | |
| passengers: ClassVar[list[str]] | |
| # If you want dynamic attributes on your class, have it | |
| # override "__setattr__" or "__getattr__" | |
| class A: | |
| # This will allow assignment to any A.x, if x is the same type as "value" | |
| # (use "value: Any" to allow arbitrary types) | |
| 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.foo = 42 # Works | |
| a.bar = 'Ex-parrot' # Fails type checking | |
| When you're puzzled or when things are complicated | |
| ************************************************** | |
| .. code-block:: python | |
| from typing import Union, Any, Optional, TYPE_CHECKING, cast | |
| # To find out what type mypy infers for an expression anywhere in | |
| # your program, 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 have to help mypy a bit by providing an explicit type annotation | |
| x: list[str] = [] | |
| x: Optional[str] = None | |
| # Use Any if you don't know the type of something or it's too | |
| # dynamic to write a type for | |
| x: 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 a "type: ignore" comment to suppress errors on a given line, | |
| # when your code confuses mypy or runs into an outright bug in mypy. | |
| # Good practice is to add a comment explaining the issue. | |
| x = confusing_function() # type: ignore # confusing_function won't return None here because ... | |
| # "cast" is a helper function that 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 fine | |
| c = 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] ... the object is not changed or casted at runtime | |
| # Use "TYPE_CHECKING" if you want to have code that mypy can see but will not | |
| # be executed at runtime (or to have code that mypy can't see) | |
| if TYPE_CHECKING: | |
| import json | |
| else: | |
| import orjson as json # mypy is unaware of this | |
| In some cases type annotations can cause issues at runtime, see | |
| :ref:`runtime_troubles` for dealing with this. | |
| See :ref:`silencing-type-errors` for details on how to silence errors. | |
| Standard "duck types" | |
| ********************* | |
| In typical Python code, many functions that can take a list or a dict | |
| as an argument only need their argument to be somehow "list-like" or | |
| "dict-like". A specific meaning of "list-like" or "dict-like" (or | |
| something-else-like) is called a "duck type", and several duck types | |
| that are common in idiomatic Python are standardized. | |
| .. code-block:: python | |
| from typing import Mapping, MutableMapping, Sequence, Iterable | |
| # Use Iterable for generic iterables (anything usable in "for"), | |
| # and Sequence where a sequence (supporting "len" and "__getitem__") is | |
| # required | |
| def f(ints: Iterable[int]) -> list[str]: | |
| return [str(x) for x in ints] | |
| f(range(1, 3)) | |
| # Mapping describes a dict-like object (with "__getitem__") that we won't | |
| # mutate, and MutableMapping one (with "__setitem__") that we might | |
| def f(my_mapping: Mapping[int, str]) -> list[int]: | |
| my_mapping[5] = 'maybe' # mypy will complain about this line... | |
| return list(my_mapping.keys()) | |
| f({3: 'yes', 4: 'no'}) | |
| def f(my_mapping: MutableMapping[int, str]) -> set[str]: | |
| my_mapping[5] = 'maybe' # ...but mypy is OK with this. | |
| return set(my_mapping.values()) | |
| f({3: 'yes', 4: 'no'}) | |
| import sys | |
| from typing import IO | |
| # Use IO[str] or IO[bytes] for functions that should accept or return | |
| # objects that come from an open() call (note that IO does not | |
| # distinguish between reading, writing or other modes) | |
| def get_sys_IO(mode: str = 'w') -> IO[str]: | |
| if mode == 'w': | |
| return sys.stdout | |
| elif mode == 'r': | |
| return sys.stdin | |
| else: | |
| return sys.stdout | |
| You can even make your own duck types using :ref:`protocol-types`. | |
| Forward references | |
| ****************** | |
| .. code-block:: python | |
| # You may want to reference a class before it is defined. | |
| # This is known as a "forward reference". | |
| def f(foo: A) -> int: # This will fail at runtime with 'A' is not defined | |
| ... | |
| # However, if you add the following special import: | |
| from __future__ import annotations | |
| # It will work at runtime and type checking will succeed as long as there | |
| # is a class of that name later on in the file | |
| def f(foo: A) -> int: # Ok | |
| ... | |
| # Another option is to just put the type in quotes | |
| def f(foo: 'A') -> int: # Also ok | |
| ... | |
| class A: | |
| # This can also come up if you need to reference a class in a type | |
| # annotation inside the definition of that class | |
| @classmethod | |
| def create(cls) -> A: | |
| ... | |
| See :ref:`forward-references` for more details. | |
| Decorators | |
| ********** | |
| Decorator functions can be expressed via generics. See | |
| :ref:`declaring-decorators` for more details. | |
| .. code-block:: python | |
| from typing import Any, Callable, TypeVar | |
| F = TypeVar('F', bound=Callable[..., Any]) | |
| def bare_decorator(func: F) -> F: | |
| ... | |
| def decorator_args(url: str) -> Callable[[F], F]: | |
| ... | |
| Coroutines and asyncio | |
| ********************** | |
| See :ref:`async-and-await` for the full detail on typing coroutines and asynchronous code. | |
| .. code-block:: python | |
| import asyncio | |
| # A coroutine is typed like a normal function | |
| async 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!" |