|Programmer's Python - Type Annotation|
|Written by Mike James|
|Monday, 20 April 2020|
Page 4 of 4
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.
var1:List[str]= var1.append("abc") var1.append(42)
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:
You can also specify the return
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.
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.
sum=Callable[[int,int],int] 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
|Last Updated ( Monday, 20 April 2020 )|