Programmer's Python - Type Annotation
Written by Mike James   
Monday, 20 April 2020
Article Index
Programmer's Python - Type Annotation
Type Hints
Subtypes & Type inference
Complex Types

Gradual Typing in Python- Type Hints

Python variables aren’t typed and a Python variable can reference any type of object.

This hasn’t changed, but now there is a move to introduce an optional type system. The idea is that you can use it if you want to, and your reward is that tools can be used to perform type checking to tell you that everything is type consistent. Nothing that you add to enable type checking has any effect on your program or the way Python runs it – but it might in the future. There are hints that a type annotated program might be subject to optimizations that a standard program isn’t.

Of course to make any of this useful you need a type checker. The mypy module is the “standard” type checker although IDEs such as PyCharm do provide some type checking as you type and understand both old and new type annotations. You can easily integrate mypy into PyCharm as an external tool. All of the error messages and behavior used in the remainder of the chapter are from mypy.

The first issue is defining what we mean by type, and it is more than most languages mean by it.

In this case type isn’t about the type of an object, it is about the type of a variable. Nothing is changed about the way Python creates objects or about the way the type function reports their type. This whole exercise is about assigning a type to a variable and so restricting the range of objects it can reference.

In the simplest case a type might correspond to a class and we might restrict a variable to only reference objects of that type or subtype, but type doesn’t have to mean the same as class.

For example, a type union can specify a set of classes and this restricts a variable to reference any one of them or any subtype. A type union doesn’t correspond to a single class, and as such it is an example of a type that doesn’t correspond to the type of an object.

A type specifies the set of objects that a variable can reference without a type error.

There is a small side issue in that we assume that the objects can be specified in terms of the attributes and operations that they support. That is, specifying a type for a variable makes it possible to assume that a set of attributes is available to use. As Python is a dynamic language and attributes can be added and deleted, this can only be an approximation.

So the task is to create an annotation system that can be used to specify exactly what objects a variable can legally reference.

This would be a simple task if it wasn’t for the requirement that subtypes should also be allowed. In other languages subtype means the same thing as subclass but not in Python.


A subtype has at least the same set of legal objects and each of the legal objects has at least the same set of attributes and operations. The idea is that if we restrict a variable to a type then we want to allow it to reference as many objects as possible that we can be sure behave in the same way i.e. have access to at least the range of attributes and operations we are making use of

This is a subtle definition. For example, consider the Boolean type. It is a subtype of int because it is restricted to two objects, 0 and 1, which are also in int and all of the same operations as int.

That is, the set of objects in the type potentially decreases in the subtype


the set of operations becomes potentially larger.

Python typing replaces the “is subtype of” by “is-consistent with” to mean that a variable of one type can reference an object of another type and its basic rules are:


  • type t1 is consistent with t2 if it is a subtype of t2

  • the type Any is consistent with every type

  • every type is consistent with Any


This may seem strange but the idea is that Any is a type that effectively has all attributes and operations and all objects. Thus you can assign anything to Any and assign an Any to anything.

Simple Types

Now it is time to look at the types that Python currently provides as part of the typing module and how they are applied. To make most of what follows work you have to:

import typing

or the parts that you are making use of.

First all of the built-in objects are types:

int	integer of arbitrary size
float	floating point number
bool	boolean value
str	unicode string
bytes	8-bit string
object	an arbitrary object

You can also add any custom objects to this list i.e. a class is also a type.

You can apply types as annotations when you first use a variable.

For example:

var1: int = 42
var2: str = "hello world"
var1 = var2

In this case var1=var2 will be a type error when the program is submitted to a checker – the program will still run without any problems, however.

In many cases the type checker will use type inference to set the type of a variable and in this case you might well not have to explicitly annotate it. In the rest of the chapter we will use explicit annotations to make clear what the types are.

Note: In earlier versions of Python a different method of applying type hints was used. Basically where annotations were allowed they were used and where they were not allowed you used comments for type hints. The modern way is to use nothing but annotations from Python 3.5 on.

Last Updated ( Monday, 20 April 2020 )