python type checking mypy collections in python

Python: type hinting your collections like a pro

When learning a new language knowing the available collections is one of the starting point.

The two most common kind of collections are lists and dictionaries. In the first, items are accessed using their position (an index), in the latter using a key.


While in python type hinting is not mandatory, it is strongly recommended to use them in combination with a static type checker like MyPy.

Type hinting a function parameter as a list

The basics: type hinting as a list

from typing using Listdef my_function(my_list: List):    ...

If you know what kind of items your list will contains (ie: a list of string, a list of int, …) you should specify it.

from typing using Listdef my_function(my_list: List[str]):    ...

More advanced: restricting the parameter allowed value

When you specify a parameter as being a list, you implicitly tell that the function might modify it.

Choosing the most adapted type hinting for a parameter is both an indication on how it is used inside the function, and what can be given as argument to the function.

For example, lets consider four variants of a simple function. They all do the same thing but have a different type hinting of the expected parameter.

Illustration by the author

We will know check what MyPy say according to what kind of parameter is given:


Let’s start with a list:

call_test_list = [i for i in range(0, 10)]variant_a(call_test_list)variant_b(call_test_list)variant_c(call_test_list)# Argument 1 to "variant_d" has incompatible type "list[int]"; expected "set[int]"variant_d(call_test_list) 

As you can see, you can give a list when the parameter is declared as a list, a sequence or an iterable.


Same with a generator:

call_test_gen = (i for i in range(0, 10))# Argument 1 to "variant_a" has incompatible type "Generator[int, None, None]"; expected "list[int]"variant_a(call_test_gen)# Argument 1 to "variant_b" has incompatible type "Generator[int, None, None]"; expected "Sequence[int]"variant_b(call_test_gen)variant_c(call_test_gen)# Argument 1 to "variant_c" has incompatible type "Generator[int, None, None]"; expected "set[int]"variant_d(call_test_gen)

The only call that was accepted was where the parameter was declared as an iterable.


Finally, using a set:

call_test_set = {i for i in range(0, 10)}# Argument 1 to "variant_a" has incompatible type "set[int]"; expected "list[int]"variant_a(call_test_set)# Argument 1 to "variant_b" has incompatible type "set[int]"; expected "Sequence[int]"variant_b(call_test_set)variant_c(call_test_set)variant_d(call_test_set)

The two functions call that were accepted are the one where the parameter were declared as an iterable or as a set.


To conclude those experimentations, I would recommend you to choose the type hinting that allows the broader choice of input values.

But do not forget that you should also choose a type hinting compatible with what you expect to do with the value inside the function!

  • Adding a value at a given position/at the end of the collection? Use a List
  • Accessing a value given its position, or iterating multiple times over the collection? Use a Sequence.
  • Adding a value only if it isn’t already present in the collection with no need to work with positions? Use a Set.
  • Iterating on values only once without changing anything? Use an Iterable

Type hinting a function parameter as a dictionary

The basics: type hinting as a dict

from typing using Listdef my_function(my_dict: dict):    ...

If you know what kind of key and values your dict will contain, you should specify it.

from typing using Dictdef my_function(my_dict: Dict[str, int]):    ...

More advanced: restricting to read only

This is something that I rarely encounter while looking at python code, but when your function will only access elements of the dictionary and not modify them, you should consider declaring the parameter as a Mapping

from typing using Mappingdef my_function(my_map: Mapping[str, int]):    ...

In this way, you tell someone using the function that they don’t have to worry about it modifying the dictionary they give as a parameter.

To conclude

Choosing the most adapted type hinting is not only about restricting what can be used, it is also about allowing more things.

Combined with a static code analyzer it is also a way to detect issues before encountering them during run time.


That’s all folks!