| .. _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 are 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 |
| |
| from typing import ClassVar |
| |
| 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 = A() |
| 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!" |