How to use type hints in Python

Jan 03, 2024#python#types

Type hints are a way of adding optional annotations to your Python code that indicate the expected types of variables, parameters, and return values. They can help you write more readable, maintainable, and bug-free code, as well as enable static type checking tools like Mypy to verify the correctness of your code.

  • They don’t change Python’s dynamic nature at runtime, but they enable static type checking for early error detection and better tooling support. This means you can still assign values of any type to variables or pass arguments of different types to functions, even if type hints are present.
  • Type hints are optional in Python, but they’re highly recommended for larger projects and collaborative development.
  • They enhance code clarity and maintainability for both humans and machines.

Python type hints support built-in types, such as str, int, float, etc., typing module types, such as List, Dict, Union, etc., and custom classes, such as Vector, Person, Animal, etc. You can use any type that is defined in your code or imported from other modules as a type hint, as long as it is consistent and compatible with the actual values.

from typing import List, Dict, Tuple, Optional

# basic type hints
name: str = "Alice"
age: int = 30
items: List[str] = ["apple", "banana", "orange"]
user_data: Dict[str, str] = {"name": "John", "email": "john@example.com"}
coordinates: Tuple[int, int] = (10, 20)
maybe_value: Optional[str] = None

# containers with specific types
numbers: List[int] = [1, 2, 3, 4, 5]
user_preferences: Dict[str, bool] = {"dark_mode": True, "notifications": False}
employee_info: Tuple[str, int, str] = ("John Doe", 35, "Software Engineer")

# function annotations
def greet(name: str) -> str:
    return f"Hello, {name}!"

# optional values
def get_user_name(user: Optional[dict]) -> Optional[str]:
    if user is not None:
        return user.get("name")
    else:
        return None

Generics and type hints

Python does not have native support for generics, but it provides a way to use them through the typing module. Generics allow functions, methods and classes to work with arguments of any type while maintaining the information on the relationships between them.

For example, you can use generics to define a function that takes a list of any type and returns the first element of the list:

from typing import List, TypeVar

T = TypeVar('T') # Declare a type variable

def first_element(items: List[T]) -> T: # Use the type variable as a generic
    return items[0]

numbers = [1, 2, 3]
names = ["Alice", "Bob", "Charlie"]

print(first_element(numbers)) # prints 1
print(first_element(names)) # prints Alice

Type checking with Mypy

Mypy is a tool that can check your code for type errors and inconsistencies based on the type hints you provide. Mypy can also infer the types of some variables and expressions without explicit annotations.

To use mypy, you need to install it using pip install mypy and then run it on your Python files using mypy file.py. You can also pass in multiple files or directories to mypy and it will type check them together. Mypy supports most of the Python language features and standard library modules, as well as many third-party libraries that have stub files (type definitions) available.

Mypy uses the syntax and semantics of type hints as defined in PEP 484 and PEP 526, which are compatible with Python 3.5 and later versions. You can use built-in types, typing module types, and custom classes as type hints, as well as more advanced features such as generics, unions, optionals, literals, and type aliases. Mypy also supports gradual typing, which means you can mix static and dynamic typing within a program, a module, or an expression, and add type hints incrementally to your code.