Toggle Menu Icon
Working with Python

Type Hints

In Python 3.5, type hints were introduced as a means for adding type information. Starting with Python 3.9, many of these types are available as built-ins, so you no longer need to import them from the typing module. This page assumes you are using Python 3.9+.

Think of type hints primarily as a documentation tool or a helper for IDEs and static analysis. The Python interpreter does not enforce type hints at runtime, but they provide valuable information for you as a developer and for various tools.

The primary benefit of using type annotations is to document your code and catch potential bugs early. They are particularly helpful with complex data structures, for example, when making a list of tuples.

my_list_of_tuples: list[tuple[str, int]] = []

Basic Types

The core basic types: str, int, float, and bool.

my_string: str = ""
my_int: int = 0
my_float: float = 0.0
my_bool: bool = True

The core collection types: list, dict, tuple, and set.

my_list: list[str] = []
my_dict: dict[str, int] = {}
my_tuple: tuple[str, int] = ("hello", 42)  # Fixed-length tuple
my_set: set[int] = set()

For variable-length tuples, use ellipsis:

my_var_tuple: tuple[int, ...] = (1, 2, 3, 4, 5)  # Variable-length tuple

Functions

To use type hints in a function for arguments and return values.

def add(a: int, b: int) -> int:
    return a + b

Use None when your function does not return a value.

def hello(name: str) -> None:
    print(f"Hello {name}")

Functions with default arguments:

def greet(name: str, greeting: str = "Hello") -> str:
    return f"{greeting}, {name}!"

Classes

To use type hints in a class.

class Point:
    x: int
    y: int

    def __init__(self, x: int, y: int) -> None:
        self.x = x
        self.y = y

    def distance_from_origin(self) -> float:
        return (self.x ** 2 + self.y ** 2) ** 0.5

Custom Types

You can use your own classes as type hints in other places:

def move_point(point: Point, dx: int, dy: int) -> Point:
    return Point(point.x + dx, point.y + dy)

my_point: Point = Point(1, 2)
new_point: Point = move_point(my_point, 3, 4)

Optional Types

For variables that can be None, use the union syntax (|) with None.

# Modern syntax (Python 3.10+)
my_optional_string: str | None = None

For older Python versions, you can still import Optional:

from typing import Optional

my_optional_string: Optional[str] = None

Union Types

Use the | operator for variables that can hold values of multiple types (Python 3.10+):

# Modern syntax
my_union: int | str = "Hello"
my_union = 123

# Multiple types
id_value: int | str | None = None

For older Python versions:

from typing import Union

my_union: Union[int, str] = "Hello"

Literal Types

Use Literal for values that must be specific literals:

from typing import Literal

def set_mode(mode: Literal["read", "write", "append"]) -> None:
    print(f"Setting mode to {mode}")

# This will work
set_mode("read")

# This will cause a type checker error
set_mode("invalid")

Any Type

Use Any when you need to opt out of type checking. Try to limit its use, as it bypasses type checking.

from typing import Any

def process_data(data: Any) -> Any:
    # This function can accept and return anything
    return data

Type Aliases

Create type aliases for complex types:

# Modern syntax using built-in types
Coordinates = list[tuple[float, float]]
UserID = int
JSONData = dict[str, Any]

my_coordinates: Coordinates = [(1.0, 2.0), (3.0, 4.0)]
user_id: UserID = 12345

Generics and TypeVar

Use generics and TypeVar to create generic functions and classes:

from typing import TypeVar, Generic

T = TypeVar('T')

def first_item(items: list[T]) -> T | None:
    return items[0] if items else None

# Usage
numbers = [1, 2, 3]
first_num = first_item(numbers)  # Type checker knows this is int | None

strings = ["a", "b", "c"]
first_str = first_item(strings)  # Type checker knows this is str | None

Generic classes:

class Stack(Generic[T]):
    def __init__(self) -> None:
        self._items: list[T] = []

    def push(self, item: T) -> None:
        self._items.append(item)

    def pop(self) -> T | None:
        return self._items.pop() if self._items else None

# Usage
int_stack: Stack[int] = Stack()
str_stack: Stack[str] = Stack()

Final Types

Use Final for constants that should not be reassigned:

from typing import Final

MAX_CONNECTIONS: Final = 100
API_VERSION: Final[str] = "v1.2.3"

# This would cause a type checker error:
MAX_CONNECTIONS = 200

When to Use Type Hints

Type hints are most beneficial when you are:

You might skip type hints for:

Tools

Most editors these days have support for type hints to provide better autocomplete and error checking. Popular type checkers include:

mypy

The original and most widely used type checker:

# Install mypy
pip install mypy

# Run mypy on a file
mypy my_file.py

# Run mypy on a directory
mypy src/

Pyright/Pylance

Microsoft’s type checker, used in VS Code’s Pylance extension:

# Install pyright
npm install -g pyright

# Run pyright
pyright src/

Configuration

You can configure type checkers with configuration files:

mypy.ini or pyproject.toml for mypy:

[mypy]
python_version = 3.9
warn_return_any = True
warn_unused_configs = True
disallow_untyped_defs = True

pyrightconfig.json for Pyright:

{
    "pythonVersion": "3.9",
    "typeCheckingMode": "strict",
    "reportMissingImports": true
}