x ai python type hint 2

By Xah Lee. Date: .

ai generated

Python Type Hints: A Comprehensive Tutorial

Type hints (introduced in Python 3.5 via PEP 484) are a powerful feature that allow you to annotate your code with expected types. They are not enforced at runtime by Python itself, but they enable static type checkers (like mypy), better IDE support (autocompletion, error detection), and improved code readability and maintainability.


1. Getting Started

Basic Syntax

def greet(name: str) -> str:
    """Return a greeting message."""
    return f"Hello, {name}!"

# Variable annotations (Python 3.6+)
age: int = 25
scores: list[float] = [95.5, 87.0, 92.3]

Running Type Checkers

Install mypy (the most popular static type checker):

pip install mypy
mypy my_script.py

2. Built-in Types

Python Type Type Hint Example Notes
int x: int = 42 Integers
float pi: float = 3.14 Floating point
str name: str = "Alice" Strings
bool is_active: bool = True Booleans
bytes data: bytes = b"hello" Byte sequences
list numbers: list[int] Python 3.9+
dict mapping: dict[str, int] Python 3.9+
tuple coords: tuple[float, float] Fixed length
set unique: set[int] Python 3.9+

For older Python versions (before 3.9) use typing module:

from typing import List, Dict, Tuple, Set

numbers: List[int] = [1, 2, 3]
mapping: Dict[str, int] = {"age": 30}

3. Common typing Constructs

Optional, Union, Any

from typing import Optional, Union, Any

# Optional = Union[T, None]
def find_user(user_id: int) -> Optional[str]:
    # Returns str or None
    ...

# Union (now | syntax in 3.10+)
def process(data: Union[int, str, None]) -> None:
    ...

# Modern syntax (Python 3.10+)
def process(data: int | str | None) -> None:
    ...

# Any = disable type checking
def risky_function(x: Any) -> Any:
    return x.something()  # No warning

Generics with Type Variables

from typing import TypeVar, Generic

T = TypeVar("T")  # Can be any type

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

# Generic class
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

4. Collections and More

from collections.abc import Sequence, Mapping, Iterable
from typing import Callable

def process_sequence(items: Sequence[int]) -> None:
    """Accepts list, tuple, etc."""
    ...

def callback_handler(func: Callable[[int, str], bool]) -> None:
    """Function that takes (int, str) and returns bool."""
    ...

5. Custom Types and Classes

from dataclasses import dataclass
from typing import TypedDict

class User:
    def __init__(self, name: str, age: int) -> None:
        self.name = name
        self.age = age

# Dataclass (Python 3.7+)
@dataclass
class Point:
    x: float
    y: float

# TypedDict (structured dictionaries)
class UserDict(TypedDict):
    name: str
    age: int
    active: bool

6. Type Aliases and NewType

from typing import TypeAlias, NewType

# Simple alias
UserId: TypeAlias = int

# NewType - creates distinct type for type checkers
UserIdStrict = NewType("UserIdStrict", int)

def get_user(user_id: UserIdStrict) -> str:
    ...

7. Advanced Features

Protocols (Structural Subtyping)

from typing import Protocol

class SupportsClose(Protocol):
    def close(self) -> None: ...

def close_resource(resource: SupportsClose) -> None:
    resource.close()

Self (Python 3.11+)

from typing import Self

class Shape:
    def scale(self, factor: float) -> Self:
        # Returns instance of the same class
        ...
        return self

Literal and Final

from typing import Literal, Final

status: Literal["success", "error", "pending"] = "success"

MAX_RETRIES: Final[int] = 5  # Cannot be reassigned

8. Practical Examples

Example 1: A Simple CLI Tool

from typing import List, Optional

def parse_args(argv: List[str]) -> Optional[int]:
    if len(argv) < 2:
        return None
    try:
        return int(argv[1])
    except ValueError:
        return None

Example 2: Generic Data Processor

from typing import TypeVar, Callable

T = TypeVar("T")
U = TypeVar("U")

def map_list(lst: list[T], func: Callable[[T], U]) -> list[U]:
    return [func(item) for item in lst]

9. Best Practices

  1. Start simple — Add hints gradually.
  2. Use Any sparingly — Only when truly dynamic.
  3. Be precise — Prefer Sequence over list when order matters but mutability doesn't.
  4. Document with types — Type hints often replace some docstring descriptions.
  5. Ignore when necessary:
def legacy_code() -> None:
    ...  # type: ignore
  1. Configure mypy with pyproject.toml:
[tool.mypy]
python_version = "3.11"
warn_return_any = true
strict = true

10. Common Pitfalls

# Bad
def append(item: int, lst: list[int] = []) -> list[int]:  # Shared default!

# Good
def append(item: int, lst: list[int] | None = None) -> list[int]:
    if lst is None:
        lst = []
    lst.append(item)
    return lst

11. Further Reading


Happy typing! 🎯

Type hints make large codebases significantly more maintainable. Start using them today — your future self (and your teammates) will thank you.


This tutorial is self-contained and covers everything from basics to advanced topics. You can copy-paste the code examples directly into your editor.