15: Type Annotation¶
Summary¶
Strong typing involves declaring every variable to have a specific type and enforcing the rule that a variable will only reference n object of that type or 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 that 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 as a separate step by a type checker
mypy being currently the most used
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
- 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
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, contravariant 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
Program¶
"""
Example program demonstrating Python type concepts:
- Type annotations (not enforced at runtime)
- Union and collection types
- Callable function types
- Generics using TypeVar
- Restricted type variables
Note: To enforce type checking, run:
mypy this_file.py
"""
from typing import List, Union, Callable, TypeVar
# -----------------------------
# Basic type annotations
# -----------------------------
def greet(name: str) -> str:
"""Return a greeting message."""
return f"Hello, {name}"
# -----------------------------
# Union type (multiple possible types)
# -----------------------------
def stringify(value: Union[int, float]) -> str:
"""Convert a number (int or float) to a string."""
return str(value)
# -----------------------------
# Collection type (List with typed items)
# -----------------------------
def sum_list(numbers: List[int]) -> int:
"""Return the sum of a list of integers."""
return sum(numbers)
# -----------------------------
# Callable type (function as parameter)
# -----------------------------
def apply_function(x: int, func: Callable[[int], int]) -> int:
"""Apply a function to an integer."""
return func(x)
# -----------------------------
# Generics with TypeVar
# -----------------------------
T = TypeVar('T')
def get_first_item(items: List[T]) -> T:
"""Return the first item from a list of any type."""
return items[0]
# -----------------------------
# Restricted TypeVar (subtypes)
# -----------------------------
Number = TypeVar('Number', int, float)
def add(a: Number, b: Number) -> Number:
"""Add two numbers (int or float only)."""
return a + b
# -----------------------------
# Main execution
# -----------------------------
if __name__ == "__main__":
print(greet("Brayden"))
print(stringify(42))
print(stringify(3.14))
print(sum_list([1, 2, 3, 4]))
# Using a Callable (lambda function)
result = apply_function(5, lambda x: x * 2)
print(result)
# Generic function
print(get_first_item(["apple", "banana", "cherry"]))
print(get_first_item([10, 20, 30]))
# Restricted TypeVar
print(add(5, 10))
print(add(2.5, 3.5))
Program Output¶
Hello, Brayden
42
3.14
10
10
apple
10
15
6.0