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