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

Things are a little more subtle than you might expect, however, as you have to take into account subtypes and type inference.

For example:

var1: int = 42
var2: float = 3.14
var2=var1

If you assign var2 to var1 you will get a type error on checking but it is fine to assign var1 to var2 because int is a subtype of float.

This is a slightly odd idea but is related to the idea that there is a natural type conversion between int and float that always works, but not going the other way. Considerations such as this only apply to simple built-in types.

You can also use classes as types.

For example:

obj:MyClass=MyClass()

now obj can only be used to reference objects created using MyClass or a subclass.

It is also worth introducing Any, even though we have already met it:

Any	dynamically typed value with an arbitrary type

A variable of type Any can be assign to any other and assigned from any other.

 

For example:

from typing import  *
var1: int = 42
var2: float = 3.14
var3:Any = "abc"
var2=var3
var2="abc"

In this case the assignment var2=var3 does not cause a type error when checked but var2=”abc” does, even though they cause the same thing to happen. You can also assign anything to var3 as it is of type Any.

Also notice that this is the first time we have need to import typing because Any is a class defined in it.

To make the distinction between class and type even more clear consider the union type. This is a set of types that the variable can reference.

For example:

var1:Union[int,float,str]
var1=3.145
var1=42
var1="abcd"

all of the assignments are valid.

If you create a union of classes then they don’t need to related in any way:

var1:Union[MyClass1,MyClass2]

can reference MyClass1 or MyClass2 even if they are not subclasses of each other. Notice that the order of types in a union doesn’t matter and a union of a list of subtypes is a subtype of the original union. For example, Union[int,str] is a subtype of Union[int,str,float]

The Optional[t1] type is an alias for Union[t1,type(None)]. If you regard None as Python’s Null then this provides a nullable type.

For example:

var1:Optional[str]="abcd"
var1=None
var2:str="abcd"
var2=None

assigning var1 to None is ok but var2 assigned to None generates a type error when you check it.

The final way to create new simple types is the NewType helper function. This can be used to add a new type that is considered a subtype of an existing type.

For example:

UserId =  NewType(‘UserId’,int)

Now UserId is a subtype of int – anywhere you can use an Int you can use a UserId but you can’t use an Int when a UserId is specified.

For example:

var1:UserId=42

will generate a type error. The problem is that you have a new type but how do you create an instance of the type? The answer is that the NewType helper function returns a function that will convert an int into a UserId type.

For example:

var1:UserId=UserId(42)

works and doesn’t cause a type error. The UserId function returns an int, but the type checker regards this as a UserId type. You can use int arithmetic on UserId types but the result is an int.

In general you can use NewType to create a notional subtype but notice that this has meaning only to the type checker as no subclasses are created in the program.

You can only use NewType to create a new type for an existing class where the existing class does the job required but you want to treat it differently. In the example above UserId is in fact an int, but you want the type checker to do the bookkeeping to keep track of UserId uses.



Last Updated ( Monday, 20 April 2020 )