Type Hints and Static Analysis
Python's type hints are optional annotations checked by mypy/pyright at static analysis time — unlike Java's mandatory compile-time types.
Introduction
In this lesson, you'll learn about type hints and static analysis in Python. Coming from Java, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In Java, you're familiar with python's type hints are optional annotations checked by mypy/pyright at static analysis time — unlike java's mandatory compile-time types..
Python has its own approach to python's type hints are optional annotations checked by mypy/pyright at static analysis time — unlike java's mandatory compile-time types., which we'll explore step by step.
The Python Way
Let's see how Python handles this concept. Here's a typical example:
from typing import TypeVar, Callable
from collections.abc import Sequence
T = TypeVar("T")
# Type hints are optional but enable static analysis
def add(a: int, b: int) -> int:
return a + b
def filter_list(items: Sequence[T], pred: Callable[[T], bool]) -> list[T]:
return [x for x in items if pred(x)]
names: list[str] = ["Alice", "Bob", "Charlie"]
long_names = filter_list(names, lambda s: len(s) > 4)
print(long_names) # ['Alice', 'Charlie']
# Union types (Python 3.10+)
def parse(val: str | int) -> int:
return int(val)
# Optional = T | None
def find(lst: list[str], key: str) -> str | None:
return next((s for s in lst if s == key), None)
# TypedDict for dict shapes
from typing import TypedDict
class Config(TypedDict):
host: str
port: int
cfg: Config = {"host": "localhost", "port": 8080}Comparing to Java
Here's how you might have written similar code in Java:
import java.util.*;
import java.util.function.*;
public class Typed {
// Types are mandatory; compiler enforces them
public static int add(int a, int b) { return a + b; }
public static <T> List<T> filterList(List<T> items, Predicate<T> pred) {
List<T> result = new ArrayList<>();
for (T item : items) if (pred.test(item)) result.add(item);
return result;
}
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob", "Charlie");
List<String> long_ = filterList(names, s -> s.length() > 4);
System.out.println(long_); // [Alice, Charlie]
}
}You may be used to different syntax or behavior.
Python type hints are optional and not enforced at runtime — use mypy/pyright for static checking
You may be used to different syntax or behavior.
Python 3.10+ uses X | Y union syntax; older versions use Optional[X] for X | None and Union[X, Y]
You may be used to different syntax or behavior.
TypeVar T creates generic type parameters; Callable[[ArgType], ReturnType] types function values
You may be used to different syntax or behavior.
TypedDict creates a typed dict shape without making a class — lighter than @dataclass for pure data
You may be used to different syntax or behavior.
Runtime type checking: isinstance(x, int) still works regardless of annotations
Step-by-Step Breakdown
1. Basic Annotations
Add : type after parameter names and -> type before the colon. Annotations don't change runtime behavior — they're metadata for tools.
public static int add(int a, int b) { return a + b; }def add(a: int, b: int) -> int:
return a + b
# Totally valid without hints (same runtime behavior):
def add_untyped(a, b):
return a + b2. Optional and Union
str | None (Python 3.10+) means the value can be a string or absent. Optional[str] is the older equivalent.
public Optional<String> find(List<String> lst, String key)from typing import Optional
def find(lst: list[str], key: str) -> str | None: # 3.10+
return next((s for s in lst if s == key), None)
# Older style:
def find_old(lst: list[str], key: str) -> Optional[str]:
...3. Generic Functions with TypeVar
TypeVar declares a type variable for generic functions. The constraint T = TypeVar('T') means 'any type, but the same type throughout'.
public static <T> List<T> filter(List<T> items, Predicate<T> pred)from typing import TypeVar
from collections.abc import Callable, Sequence
T = TypeVar("T")
def filter_seq(items: Sequence[T], pred: Callable[[T], bool]) -> list[T]:
return [x for x in items if pred(x)]4. Running mypy
mypy checks type hints statically. Use --strict for strictest mode. Gradual typing: add hints to new code; leave legacy code untyped.
# Java: javac catches type errors at compile time# Install and run:
pip install mypy
mypy mymodule.py # basic check
mypy --strict mymodule.py # strict: all functions must be typed
# pyproject.toml:
# [tool.mypy]
# strict = true
# python_version = "3.12"Common Mistakes
When coming from Java, developers often make these mistakes:
- Python type hints are optional and not enforced at runtime — use mypy/pyright for static checking
- Python 3.10+ uses X | Y union syntax; older versions use Optional[X] for X | None and Union[X, Y]
- TypeVar T creates generic type parameters; Callable[[ArgType], ReturnType] types function values
Key Takeaways
- Type hints are optional annotations — mypy/pyright check them statically; Python runtime ignores them
- X | None (3.10+) or Optional[X] for nullable values; X | Y for union types
- TypeVar T creates generic type parameter; Callable[[A], R] types function values
- Run 'mypy --strict' for full coverage; start gradual — annotate new functions first