Type Annotations in Python 3.8

Martin Thoma
Jun 22 · 5 min read
Image for post
Image for post
Image Source: Benjamin Hell

One reason why Python is so easy to get started with is that it has dynamic types. You don’t have to specify the type of a variable, you just use variables as labels for containers of data. But in bigger projects, having types is helpful. If you have an undocumented function without types and maybe crappy variable naming, new developers will have a hard time. Luckily, variable annotations were added in Python 3.6 with PEP 526 🎉

This article is written in such a way that you can easily stop after the “mypy” section and take only a look at individual section then.

Hello, Typed Annotated World!

So you can simply use the pattern

def some_function(param_name : typename) -> return_type_name:
... # whatever the function does

Having type annotations is nice, but you need to check them! The Python runtimes do not do that, no matter if you use cPython, pypy or something more exotic.

Image for post
Image for post
Source: The mypy project, #383

Type Checking with mypy

$ mypy . --ignore-missing-imports
Success: no issues found in 1 source file

The --ignore-missing-imports flag is necessary, because otherwise you will get a lot of messages like this:

error: Skipping analyzing ‘setuptools’: found module but no type hints or library stubs

In order to make it more convenient, I usually add a setup.cfg file in which I specify that I always want this flag to be applied:

[mypy]
ignore_missing_imports=true

Then you can pip install pytest-mypy and make sure mypy is always executed when you run pytest by adding this section to your setup.cfg:

[tool:pytest]
addopts = --mypy

It is important to note that the Python community and also mypy assumes that you come from a non-type annotated code base. They want to make it easy to you to switch to an annotated code and thus support gradual typing. However, this means that you might miss errors if you don’t annotate your code! Mypy has a lot of flags to help you to make the move. You don’t need to annotate everything.

typing: List, Dict, Tuple, Any

Similarly, you can annotate that a dictionary maps strings to integers by Dict[str, int] . So List, Dict and Tuple are generics. Any is just a way to specify that you could have arbitrary data in those containers. It is reasonable to use Any in the beginning when you start to add type annotations to a bigger code base.

Stop Type Checking

typing.Any : Every type is of type Any.

typing.cast(SomeClass, variable) : Sometimes mypy is not smart enough, so you can tell it which type you have. I did that a couple of times before I knew about typing.overload . Alternatively, you can also add assert isinstance(variable, Someclass)

# type: ingore : Explicitly tell the typechecker to ignore that line

typing: Union and Optional

As it happens pretty often that you need to accept some type and None , there is also typing.Optional . Optional[SomeType] is the same as Union[SomeType, None] .

typing: List vs Sequence

typing: Dict vs Mapping

typing: TypedDict

from typing import TypedDict

class Movie(TypedDict):
name: str
year: int
movie: Movie = {'name': 'Blade Runner',
'year': 1982}

Many more Types

Custom Types: Not all Strings are Created Equal

Especially for IDs I have seen this to become messy. I think it’s pretty ridiculous to create an own class for those different string types as creating a class is usually development and maintenance overhead. So, what do you do?

Don’t worry, typing got you covered!

from typing import NewType
UserId = NewType("UserId", str)

typing.overload

Protocols

Note that there is no function body. After that definition, you can then use SupportsClose like any type.

The cool part is that the class Foo has no explicit relationship to SupportsClose ! It is only related by its structure!

Type comments

However, you might want to disable type checking for single lines:

# type: ignore

Stub files

def fib_list(n: int) -> List[int]: ...

There are also libraries like boto3-stubs which provide type annotations.

pyright, pyre, pytype

Install them:

$ pip install pyre-check pytype# Yes, pyright is written in TypeScript...
$ npm install -g pyright

Run them:

$ pyright .
$ pytype .

pyright was complaining a lot about stuff that is actually correct.

pydantic

Which gives:

[User(name='user1', age=15), User(name='user2', age=28)]
[{"name": "user1", "age": 15}, {"name": "user2", "age": 28}]

FastAPI uses pydantic directly.

A cool thing about pydantic are the constrained types: PositiveFloat, NegativeInt, constr, …

See also

  1. Andreas Dewes: Type Annotations in Python 3: Whats, whys & wows! at EuroPython Conference, 2017. On YouTube.
  2. Carl Meyer: Type-checked Python in the real world at PyCon, 2018. On YouTube.

Analytics Vidhya

Analytics Vidhya is a community of Analytics and Data…

Martin Thoma

Written by

Machine Learning developer, hiker, blogger

Analytics Vidhya

Analytics Vidhya is a community of Analytics and Data Science professionals. We are building the next-gen data science ecosystem https://www.analyticsvidhya.com

Martin Thoma

Written by

Machine Learning developer, hiker, blogger

Analytics Vidhya

Analytics Vidhya is a community of Analytics and Data Science professionals. We are building the next-gen data science ecosystem https://www.analyticsvidhya.com

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store