Python type hints
Python has support for optional "type hints". These "type hints" are a special syntax that allow declaring the type of a variable. By declaring types for your variables, editors and tools can give you better support.
Variables
This is how you declare the type of a variable type in Python 3.6+:
You don't need to initialize a variable to annotate it:
This is useful in conditional branches:
Built-in types
For simple built-in types, just use the name of the type:
For collections, the type of the collection item is in brackets (Python 3.9+):
Note: In Python 3.8 and earlier, the name of the collection type is capitalized, and the type is imported from the typing
module:
For mappings, we need the types of both keys and values:
For tuples of fixed size, we specify the types of all the elements:
For tuples of variable size, we use one type and ellipsis:
Use Optional[]
for values that could be None
:
Functions
This is how you annotate a function definition:
And here's how you specify multiple arguments:
Add default value for an argument after the type annotation:
This is how you annotate a callable (function) value:
A generator function that yields ints is secretly just a function that returns an iterator of ints, so that's how we annotate it:
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:
More advanced stuff
Use Union
when something could be one of a few types:
Use Any
if you don't know the type of something or it's too dynamic to write a type for:
This makes each positional argument 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)
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.
Use Iterable
for generic iterables (anything usable in for
), and Sequence
where a sequence (supporting len
and __getitem__
) is required:
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' # if we try this, mypy will throw an error...
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'})
Classes
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:
You can use the ClassVar
annotation to declare a class variable:
You can also declare the type of an attribute in __init__
: