.. _cheat-sheet-py3:

Type hints cheat sheet (Python 3)
=================================

This document is a quick cheat sheet showing how the
`PEP 484 <https://www.python.org/dev/peps/pep-0484/>`_ type
annotation notation represents various common types in Python 3.

.. note::

   Technically many of the type annotations shown below are redundant,
   because mypy can derive them from the type of the expression.  So
   many of the examples have a dual purpose: show how to write the
   annotation, and show the inferred types.


Variables
*********

Python 3.6 introduced a syntax for annotating variables in
`PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_ and
we use it in most examples.

.. code-block:: python

   # This is how you declare the type of a variable type in Python 3.6
   age: int = 1

   # In Python 3.5 and earlier you can use a type comment instead
   # (equivalent to the previous definition)
   age = 1  # type: int

   # You don't need to initialize a variable to annotate it
   a: int  # Ok (no value at runtime until assigned)

   # The latter is useful in conditional branches
   child: bool
   if age < 18:
       child = True
   else:
       child = False


Built-in types
**************

.. code-block:: python

   from typing import List, Set, Dict, Tuple, Optional

   # For simple built-in types, just use the name of the type
   x: int = 1
   x: float = 1.0
   x: bool = True
   x: str = "test"
   x: bytes = b"test"

   # For collections, the name of the type is capitalized, and the
   # name of the type inside the collection is in brackets
   x: List[int] = [1]
   x: Set[int] = {6, 7}

   # Same as above, but with type comment syntax
   x = [1]  # type: List[int]

   # For mappings, we need the types of both keys and values
   x: Dict[str, float] = {'field': 2.0}

   # For tuples, we specify the types of all the elements
   x: Tuple[int, str, float] = (3, "yes", 7.5)

   # Use Optional[] for values that could be None
   x: Optional[str] = some_function()
   if x is not None:
       print(x)


Functions
*********

Python 3 supports an annotation syntax for function declarations.

.. code-block:: python

   from typing import Callable, Iterable, Union, Optional, List

   # 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

   # Add default value for an argument after the type annotation
   def f(num1: int, my_float: float = 3.5) -> float:
       return num1 + my_float

   # This is how you annotate a callable (function) value
   x: Callable[[int, float], float] = f

   # A generator function that yields ints is secretly just a function that
   # returns an iterable (see below) of ints, so that's how we annotate it
   def f(n: int) -> Iterable[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='',
                  body: Optional[List[str]] = None
                  ) -> bool:
       ...

   # An argument can be declared positional-only by giving it a name
   # starting with two underscores:
   def quux(__x: int) -> None:
       pass

   quux(3)  # Fine
   quux(__x=3)  # Error


When you're puzzled or when things are complicated
**************************************************

.. code-block:: python

   from typing import Union, Any, List, Optional, 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'

   # Use Union when something could be one of a few types
   x: List[Union[int, str]] = [3, 5, "test", "fun"]

   # 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()

   # If you initialize a variable with an empty container or "None"
   # you may have to help mypy a bit by providing a type annotation
   x: List[str] = []
   x: Optional[str] = None

   # This makes each positional arg and each keyword arg a "str"
   def call(self, *args: str, **kwargs: str) -> str:
       request = make_request(*args, **kwargs)
       return self.do_api_query(request)

   # 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 comment every "ignore" with a bug link
   # (in mypy, typeshed, or your own code) or an explanation of the issue.
   x = confusing_function()  # type: ignore  # https://github.com/python/mypy/issues/1167

   # "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 (no runtime check)
   reveal_type(c)  # -> Revealed type is 'builtins.list[builtins.str]'
   print(c)  # -> [4]; the object is not cast

   # If you want dynamic attributes on your class, have it override "__setattr__"
   # or "__getattr__" in a stub or in your source code.
   #
   # "__setattr__" allows for dynamic assignment to names
   # "__getattr__" allows for dynamic access to names
   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


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, List, Set

   # 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_dict: Mapping[int, str]) -> List[int]:
       return list(my_dict.keys())

   f({3: 'yes', 4: 'no'})

   def f(my_mapping: MutableMapping[int, str]) -> Set[str]:
       my_mapping[5] = 'maybe'
       return set(my_mapping.values())

   f({3: 'yes', 4: 'no'})


Classes
*******

.. code-block:: python

   class MyClass:
       # You can optionally declare instance variables in the class body
       attr: int
       # This is an instance variable with a default value
       charge_percent: int = 100

       # 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) -> None:
           ...

       # For instance methods, omit type for "self"
       def my_method(self, num: int, str1: str) -> str:
           return num * str1

   # User-defined classes are valid as types in annotations
   x: MyClass = MyClass()

   # You can use the ClassVar annotation to declare a class variable
   class Car:
       seats: ClassVar[int] = 4
       passengers: ClassVar[List[str]]

   # You can also declare the type of an attribute in "__init__"
   class Box:
       def __init__(self) -> None:
           self.items: List[str] = []


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 countdown35(tag: str, count: int) -> str:
       while count > 0:
           print('T-minus {} ({})'.format(count, tag))
           await asyncio.sleep(0.1)
           count -= 1
       return "Blastoff!"


Miscellaneous
*************

.. code-block:: python

   import sys
   import re
   from typing import Match, AnyStr, IO

   # "typing.Match" describes regex matches from the re module
   x: Match[str] = re.match(r'[0-9]+', "15")

   # Use IO[] for functions that should accept or return any
   # object that comes from an open() call (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

   # Forward references are useful if you want to reference a class before
   # it is defined
   def f(foo: A) -> int:  # This will fail
       ...

   class A:
       ...

   # If you use the string literal 'A', it will pass as long as there is a
   # class of that name later on in the file
   def f(foo: 'A') -> int:  # Ok
       ...
