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

Complex Types

So far we have been focusing on the idea of type as specifying the methods that are available and operations that can be applied. There is another more complex aspect of type. Some objects have attributes that can vary in type. For example any container object can have a range types as items.

In such cases the type of the item can also affect what methods and operations can be applied to the object.

For example, a List of strings supports the same List operations as a List of ints but a different set of operations on its elements. In such cases the type of an object has to include the type of the elements.

The built-in container types are, with examples of element types included:

List[str]	     list of str objects
Tuple[int, int]   tuple of two int objects 
Tuple[int, …]     tuple of an arbitrary number of int objects
Dict[str, int]    dictionary from str keys to int values
Iterable[int]	     iterable object containing ints
Sequence[bool]    sequence of booleans

These are examples of generic types, more of which in the next section, but for the moment all that matters is that you can see that to ensure compatibility you have to specify the type of the elements.

For example:


The first line creates a variable which is supposed to reference a list of strings. Some type systems would rule out assigning an empty list to a list of strings but currently the Python type checker allows it. Next, a string is added to the list which is fine, but when we try to add an int the type checker reports a type error.

Notice that we are using different information about types here. The List is indeed a List and hence we can safely use List methods such as append, but the elements are also restricted to strings and hence string operations.

Notice that when you declare a List to be of type List[int] or List[str] nothing changes about the way the List works in your program, it is the same old list you have been using all the time and it can store lists of mixed types. It is only the type checker that takes any notice of List[int].

The Callable – a Complex Type

A very special complex type is the callable.

As we already know, a callable is an object that can be called like a function. In this case what matters are the types of its parameters and its return type.

You can create a type for a callable using:

Callable[[parameter list],return]

You can also specify the return

For example:


defines a type that is a callable with two int parameters returning an int.

A function object that is an instance of the type would be defined as:

def myFunc(a:int,b:int)->int:
    return a+b

You can assign a callable to a variable of a compatible type without a type error:


The main intended use for callable types is to allow type checking when a function is passed as a parameter.

For example:

def myFunc2(f:Callable[[int,int],int])-> int:
    return f(2,3)



don’t cause any type errors.

Of all of the type annotations, the callable is the most practical – it is easy to apply and catches many simple errors.

The type alias is also worth knowing about because it can be used to create a simpler name for a complicated type. All you have to do is assign a type to a variable and then you can use the variable as if it was a reference to the type – which it is.

For example:

def myFunc2(f:sum)-> int:
    return f(2,3)

works exactly as before, but now the type name is sum.

A careful use of aliases can make a type annotated program much easier to understand.

Section in rest of chapter but not included in this extract

  • Custom Generics
  • Restricted Type Variables and Generic Functions
  • Co and Contravariance
  • Co & Contravariant Generics
  • Reflections on Type Annotations
  • How Type Annotations Work



  • Strong typing involves declaring every variable to have a specific type and enforcing the rule that a variable will only reference an object of that type or of a subtype.

  • In most languages class and type are synonymous and subtype is the same as subclass, but not in Python which allows types to be more than just class based.

  • You assign a variable a type using an annotation with an object from the typing module.

  • Type isn’t enforced by Python but by a type checker, mypy is currently the most used, as a separate step. In theory type annotations do not change the way your program works, only the response of the type checker.

  • The typing module introduces a set of simple types including compound types such as union.

  • As well as simple types there are also complex types where the type of an object is dependent on the types of its attributes. The most common example is of a collection, where the collection has a type and the items it contains also have a type.

  • One special and very useful complex type is the callable which allows the creation of a function type which includes the type of its parameters and its return.

  • To deal with complex types that can work with different types of item we have to introduce the idea of a type variable and generics.

  • To make generics work reasonably we have to also introduce the idea of a restricted type variable i.e. one that can be assumed to be of a particular type or its subtype.

  • Once we have generics and complex types we also have to deal with the distinctions between covariance, contravariance and invariance.

  • What starts out being simple evolves into something much more complicated. A limited use of type checking seems like the best way to work with Python at the moment.

Programmer's Python
Everything is an Object
Second Edition

Is now available as a print book: Amazon



  1. Get Ready For The Python Difference
  2. Variables, Objects and Attributes
  3. The Function Object
  4. Scope, Lifetime and Closure
  5. Advanced Functions
  6. Decorators
  7. Class, Methods and Constructors
      Extract 1: Objects Become Classes ***NEW!
  8. Inside Class
  9. Meeting Metaclasses
  10. Advanced Attributes
  11. Custom Attribute Access
  12. Single Inheritance
  13. Multiple Inheritance
  14. Class and Type
  15. Type Annotation
  16. Operator Overloading
  17. Python In Visual Studio Code

 Extracts from the first edition

Advanced Attributes

Related Articles

Creating The Python UI With Tkinter

Creating The Python UI With Tkinter - The Canvas Widget

The Python Dictionary

Arrays in Python

Advanced Python Arrays - Introducing NumPy




or email your comment to:

To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.



Last Updated ( Monday, 20 April 2020 )