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

-

  • [01 Type System & Annotations](/05-py3/01-fundamentals/(01-type-system-annotations/) - Basic type hints
  • [02 Execution Model & Compilation](/05-py3/09-bytecode-and-execution/(02-execution-model-compilation/) - Runtime behavior