Python Basics: Type Hints

Learn modern type annotations, generics, Optional/Union, Callable, TypedDict, Protocol, and best practices with static checkers.

Quiz progress
0 / 10 answered
Contents

What Type Hints Are (and Aren’t) #

Essentials:
  • Hints add optional static types. Python doesn’t enforce them at runtime by default.
  • Annotations live in __annotations__ and are consumed by tools (mypy, pyright, IDEs).
  • Annotate function signatures and important variables for clarity and tooling.
  • Forward references: use quotes (e.g., "User") or from __future__ import annotations to postpone evaluation.
def area(r: float) -> float:
    return 3.14159 * r * r

x: int = 3               # variable annotation
print(area(2.0))
print(__annotations__)
“Type hints document your intent and let tools catch mistakes—without changing Python’s runtime semantics.”
— Practical Typing Wisdom

Quiz: Type Hint Basics

1. At runtime, type hints…
2. Choose the best signature for a function that takes a float and returns a float:

Collections & Generics #

Modern syntax (3.9+):
  • Use built-in generics: list[int], dict[str, int], set[str], tuple[int, str].
  • Type aliases: UserId = int, StrList = list[str].
  • Any = opt out; object = most general supertype (accepts anything but you must narrow to use).
from typing import Any

names: list[str] = ["ada", "grace"]
scores: dict[str, int] = {"math": 95}
raw: Any = "could be anything"
top3: tuple[int, int, int] = (10, 9, 8)

Quiz: Collections & Generics

3. On Python 3.9+, idiomatic way to type a list of strings is…
4. Dict mapping course names to scores (ints):

Unions, Optional, Callable & Tuples #

Handy types:
  • Union[X, Y] or X | Y (3.10+)
  • Optional[X] means X | None
  • Callable[[ArgTypes...], Return]
  • Tuples: tuple[int, str] (fixed length) or tuple[int, ...] (var-length)
from typing import Optional, Union, Callable

maybe_id: Optional[int] = None             # same as int | None
text_or_id: Union[str, int] = "abc"        # or: str | int
op: Callable[[int, int], int]
op = lambda a, b: a + b
pair: tuple[str, float] = ("price", 9.99)

Quiz: Unions, Optional, Callable & Tuples

5. Which are equivalent to “int or None”? (Select all)
6. Type for a function that takes two ints and returns a str:
7. Idiomatic (3.10+) type for a pair like ("price", 9.99) is…

Dataclasses, TypedDict & Protocol #

Structured typing options:
  • @dataclass uses annotations to define fields and generate methods; types aren’t enforced automatically.
  • TypedDict: dicts with a typed shape (checked by type checkers; still a normal dict at runtime).
  • Protocol: structural (duck) typing—match by methods/attributes, not inheritance.
from dataclasses import dataclass
from typing import TypedDict, Protocol

@dataclass
class User:
    name: str
    age: int = 0

class UserTD(TypedDict):
    id: int
    name: str

class Greeter(Protocol):
    def greet(self, name: str) -> str: ...

def say_hello(g: Greeter) -> str:
    return g.greet("Ada")

Quiz: Dataclasses, TypedDict & Protocol

8. Which is true about @dataclass?
9. Which statement about TypedDict is correct?

Best Practices #

Guidelines:
  • Annotate function signatures and public APIs; key variables that aid readability.
  • Prefer modern built-in generics (list[int]) on 3.9+.
  • Use Any sparingly—prefer precise unions or protocols.
  • Keep hints simple; don’t sacrifice readability for cleverness.
  • Run a checker (mypy/pyright) in CI to get value from your hints.

Quiz: Best Practices

10. Which are good practices? (Select all)

Final Quiz & Summary #

Review your performance and revisit questions you missed.

Category: python · Lesson: type-hints-basics
Learning by Examples
Edit the code and see the result in the console panel.
# Experiment with type hints at runtime.
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Optional, Union, Callable, TypedDict, Protocol, get_type_hints

def greet(name: str, times: int = 1) -> str:
    return " ".join([f"Hello, {name}!"] * times)

print("greet annotations:", get_type_hints(greet))

# Variables
count: int = 3
maybe_id: Optional[int] = None
price_pair: tuple[str, float] = ("price", 9.99)

# Callable
adder: Callable[[int, int], int] = lambda a, b: a + b
print("adder(2,3) =", adder(2,3))

# TypedDict
class UserTD(TypedDict):
    id: int
    name: str

u: UserTD = {"id": 1, "name": "Ada"}
u["id"] += 1
print("UserTD:", u)

# Protocol (duck typing)
class Greeter(Protocol):
    def greet(self, name: str) -> str: ...

class Friendly:
    def greet(self, name: str) -> str:
        return f"Hi {name}"

def welcome(g: Greeter) -> str:
    return g.greet("Grace")

print("welcome:", welcome(Friendly()))

# Dataclass picks up annotations for fields
@dataclass
class Point:
    x: float
    y: float = 0.0

p = Point(1.5)
print("Point:", p)

# Demonstrate that hints are not enforced automatically:
def expects_int(n: int) -> None:
    print("n squared:", n*n)

expects_int(5)          # ok
expects_int("5")        # still runs in CPython; static tools would flag this