Skip to content

Type Hints & Validation

Overview

Type hints improve code clarity and enable tooling: - Type annotations: def func(x: int) -> str: - Generic types: List[int], Dict[str, float] - Optional and Union: Optional[int], Union[int, str] - Type checking: mypy, pyright validate types


Basic Type Hints

Function Annotations

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

# Optional types
def greet(name: str = None) -> None:
    if name:
        print(f"Hello, {name}")
    else:
        print("Hello")

# Union types
from typing import Union

def process(value: Union[int, str]) -> str:
    return str(value)

# Multiple returns
def divide(a: float, b: float) -> Union[float, str]:
    if b == 0:
        return "Error"
    return a / b

Collection Types

from typing import List, Dict, Tuple, Set

def process_numbers(numbers: List[int]) -> Dict[str, int]:
    return {
        'sum': sum(numbers),
        'count': len(numbers),
        'max': max(numbers)
    }

def coordinates() -> Tuple[float, float]:
    return (1.0, 2.0)

def unique_tags(articles: List[Dict[str, str]]) -> Set[str]:
    tags = set()
    for article in articles:
        if 'tags' in article:
            tags.update(article['tags'].split(','))
    return tags

Generic Types

TypeVar for Generic Functions

from typing import TypeVar, List

T = TypeVar('T')  # Generic type variable

def get_first(items: List[T]) -> T:
    """Generic function - works with any list type."""
    return items[0]

# Works with different types
numbers = get_first([1, 2, 3])      # Type is int
strings = get_first(['a', 'b', 'c'])  # Type is str

Generic Classes

from typing import Generic, TypeVar

T = TypeVar('T')

class Stack(Generic[T]):
    """Generic stack."""

    def __init__(self):
        self._items: List[T] = []

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

    def pop(self) -> T:
        return self._items.pop()

# Use it
int_stack: Stack[int] = Stack()
int_stack.push(1)
int_stack.push(2)
print(int_stack.pop())  # 2

Type Checking with mypy

Running mypy

# Install mypy
pip install mypy

# Check file
mypy script.py

# Check with strict mode
mypy --strict script.py

Type Checking Examples

# mypy checks these

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

# ✓ Correct
result = greet("Alice")

# ✗ Wrong - mypy reports error
result = greet(42)  # Error: Argument 1 has incompatible type "int"; expected "str"

Protocols: Structural Subtyping

Protocol Definition

from typing import Protocol

class Drawable(Protocol):
    """Protocol for drawable objects."""

    def draw(self) -> None:
        ...

class Circle:
    def draw(self) -> None:
        print("Drawing circle")

class Square:
    def draw(self) -> None:
        print("Drawing square")

def render(obj: Drawable) -> None:
    """Works with any object that has draw() method."""
    obj.draw()

# Works with Circle and Square (no inheritance needed!)
render(Circle())  # ✓
render(Square())  # ✓

ML Type Hints

PyTorch Model Typing

from typing import Tuple, Optional
import torch
import torch.nn as nn

def forward_pass(
    model: nn.Module,
    x: torch.Tensor,
    training: bool = False
) -> torch.Tensor:
    """Type-hinted forward pass."""
    if training:
        model.train()
    else:
        model.eval()

    with torch.no_grad():
        return model(x)

def train_step(
    model: nn.Module,
    x: torch.Tensor,
    y: torch.Tensor,
    optimizer: torch.optim.Optimizer,
    loss_fn: nn.Module
) -> Tuple[torch.Tensor, float]:
    """Type-hinted training step."""
    optimizer.zero_grad()

    logits = model(x)
    loss = loss_fn(logits, y)

    loss.backward()
    optimizer.step()

    return logits, loss.item()

DataLoader Typing

from typing import Iterator, Tuple
from torch.utils.data import DataLoader, TensorDataset

def create_dataloader(
    X: torch.Tensor,
    y: torch.Tensor,
    batch_size: int = 32
) -> DataLoader:
    """Create typed dataloader."""
    dataset = TensorDataset(X, y)
    return DataLoader(dataset, batch_size=batch_size, shuffle=True)

def iterate_batches(
    dataloader: DataLoader
) -> Iterator[Tuple[torch.Tensor, torch.Tensor]]:
    """Iterate with type hints."""
    for batch_x, batch_y in dataloader:
        yield batch_x, batch_y

TypedDict for Configuration

Typed Dictionaries

from typing import TypedDict

class ConfigDict(TypedDict):
    """Type-safe configuration dictionary."""
    host: str
    port: int
    debug: bool
    timeout: float

def load_config(path: str) -> ConfigDict:
    """Load configuration with type hints."""
    import json
    with open(path) as f:
        config = json.load(f)
    return config

# mypy ensures correct structure
config: ConfigDict = load_config('config.json')
print(config['host'])    # ✓ OK
print(config['unknown']) # ✗ Error: Unknown key

Summary: Type Hints Benefits

Benefit Example
IDE support Autocomplete works better
Error detection mypy catches mistakes
Self-documenting Types show intent
Refactoring Rename safely