Modern PythonLesson 2 of 3
Type Hints
Python's type hints look a lot like TypeScript annotations, with one crucial difference: Python does not enforce them at runtime. They are checked by separate tools (mypy, pyright, or your editor) before you run the code, and they double as documentation. Code with wrong hints still runs.
Use the built-in types directly: name: str, scores: list[int], ages: dict[str, int], point: tuple[float, float]. Since Python 3.9 you no longer need typing.List or typing.Dict, and since 3.10 unions are written with |: str | int.
Careful with "optional". In TypeScript, title?: string means the parameter can be left out. In Python, title: str | None only says the value may be None; the caller must still pass it unless you also give it a default: title: str | None = None. The old spelling Optional[str] means exactly str | None, which is why the name confuses people.
Protocol classes are Python's answer to TypeScript interfaces. Any class that has the right methods satisfies the Protocol without inheriting from it. This is structural typing, the same model TypeScript uses.
Python 3.12 added a cleaner syntax for generics: def first[T](items: list[T]) -> T, and type aliases with the type statement: type UserId = int | str.
Quick reference
| JavaScript | Python | Note |
|---|---|---|
| x: string | x: str | Built-in type names differ |
| number[] | list[int] / list[float] | Built-in generics (3.9+) |
| string | number | str | int | Union with | (3.10+) |
| title?: string | title: str | None = None | Needs a default to be omittable |
| interface Drawable { } | class Drawable(Protocol): | Structural typing |
| (a: T) => R | Callable[[T], R] | from collections.abc |
| function first<T>(xs: T[]): T | def first[T](xs: list[T]) -> T: | Generics syntax (3.12+) |
| tsc --noEmit | mypy / pyright | Not enforced at runtime |
Side by side
// TypeScript
let name: string = "Alice";
let scores: number[] = [1, 2, 3];
let id: string | number;
function greet(name: string, title?: string): string {
return title ? title + " " + name : name;
}
interface Drawable {
draw(): string;
}# Python type hints
from collections.abc import Callable
from typing import Protocol
name: str = "Alice"
scores: list[int] = [1, 2, 3]
id_val: str | int = "abc"
def greet(name: str, title: str | None = None) -> str:
return f"{title} {name}" if title else name
# Protocol = structural typing (like a TS interface)
class Drawable(Protocol):
def draw(self) -> str: ...
class Circle: # no inheritance needed
def draw(self) -> str:
return "()"
def render(shape: Drawable) -> None:
print(shape.draw())
render(Circle()) # OK for the type checker
# Callable type
def apply(fn: Callable[[int, int], int], a: int, b: int) -> int:
return fn(a, b)
# Generic function (3.12+)
def first[T](items: list[T]) -> T:
return items[0]Exercise
Real Python, running in your browserAdd type hints to every parameter and return value:
def process(items, transformer, default_val):
results = []
for item in items:
try:
results.append(transformer(item))
except Exception:
results.append(default_val)
return results
def find_user(users, user_id):
for user in users:
if user["id"] == user_id:
return user
return NoneCtrl + Enter runCtrl + Shift + Enter checkTab indentEsc then Tab to leave the editorCtrl + / comment