LESSON 35 · INTERMEDIATE4 min read

Introduction to Type Hints

Add annotations that document real contracts, run a static checker, and model records with dataclasses.

Python 3.12+Free to readModule 7 of 19
YOUR NEXT SMALL WINS

By the end, you’ll be able to…

  • Explain what annotations do and do not enforce at runtime.
  • Annotate parameters, return values, and optional data precisely.
  • Run mypy and interpret its most common errors.
  • Use dataclasses for typed records with safe defaults.

01Annotations describe a contract

Python remains dynamically typed: an annotation is metadata, not a runtime guard. A function declared to return float can still return a string unless your code prevents it. The value of the annotation is that readers, editors, and static checkers can reason about the intended contract before the program runs.

Annotate the boundary callers depend on: parameters, return values, and public attributes. Internal local variables rarely need annotations because the checker can infer them. Start with the functions that exchange data between modules; those are the contracts most likely to be misunderstood.

average.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

02Choose precise types

Use int, float, str, bool, and None for ordinary values. A value that may be absent is written X | None in modern Python, or Optional[X] in older code. Collections are described by their contents: list[str], dict[str, int], and tuple[str, int] say much more than a bare list or dict.

Prefer the abstract input type when the function only reads. A function that iterates over a sequence can accept Sequence[str] instead of list[str], which lets callers pass a tuple or another sequence. The return type should be concrete enough for callers to use without guessing.

optional_lookup.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

03Run a static checker

mypy reads annotations without executing the program. Install it into the project environment and run it against a file or package. A typical error names the expected type, the received type, and the line. Fix the implementation or the annotation, whichever is actually wrong; do not silence the error reflexively.

Adopt checking gradually if a codebase has no annotations. Start with the modules that change most often, add return types first, and keep the checker green. A strict mode that reports hundreds of pre-existing errors is less useful than a smaller, enforced contract.

mypy_workflow.txtShell
py -m pip install mypypy -m mypy average.pypy -m mypy --strict package/

04Dataclasses make typed records

A dataclass generates initialization, representation, and equality methods from field declarations. It pairs naturally with annotations: the same declaration documents the record and gives the checker something to verify. Use field(default_factory=list) for a mutable default, because a plain [] would be shared by every instance.

Use frozen=True when the record should not change after construction, and slots=True when you want a compact object with a fixed set of attributes. A dataclass does not validate field values by default; add __post_init__ checks or use a validation library at the boundary when the rules matter.

task_record.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
LET IT CLICK

The ideas worth keeping.

  • Annotations document contracts; they do not enforce runtime behavior.
  • Precise collection and optional types make interfaces easier to use.
  • Dataclasses pair typed fields with generated initialization and representation.
TURN UNDERSTANDING INTO SOMETHING YOU CAN DO

Make it yours.

Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.

01

Annotate a price parser

Write parse_price(text: str) -> float that strips whitespace, rejects an empty string, converts to float, and rejects a negative value. Include annotations and demonstrate a successful call.

your_turn_1.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.pyPython
def parse_price(text: str) -> float:    cleaned = text.strip()    if not cleaned:        raise ValueError("price cannot be empty")    value = float(cleaned)    if value < 0:        raise ValueError("price cannot be negative")    return valueprint(parse_price(" 19.95 "))

The annotation states the accepted input and the promised result. The checks enforce the parts a type checker cannot: non-empty text and a nonnegative amount.

02

Model a task with safe defaults

Define a Task dataclass with title: str, done: bool = False, and tags: list[str] using field(default_factory=list). Create two tasks and prove they do not share the same tag list.

your_turn_2.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.pyPython
from dataclasses import dataclass, field@dataclassclass Task:    title: str    done: bool = False    tags: list[str] = field(default_factory=list)first = Task("Read")second = Task("Write")first.tags.append("docs")print(first.tags, second.tags)

The output is ['docs'] [] because default_factory creates a new list for each instance. A shared default would make the second task appear to have the first task's tags.

A SMALL QUESTION. A USEFUL NEXT STEP.

What stayed with you?

Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.

A LITTLE ACTIVE RECALL0 / 3 answered
01What happens when a function annotated to return int actually returns a string?
02Which annotation says a value is a string or absent?
03Why use field(default_factory=list) in a dataclass?
⚡CODING CHALLENGE
🔒Required to Mark Lesson Complete

Annotate a price parser

Write parse_price(text: str) -> float that strips whitespace, rejects an empty string, converts to float, and rejects a negative value. Include annotations and demonstrate a successful call.

EXPECTED OUTPUT
parse_price(" 19.95 ")
main.pyPython 3
Ctrl + ↵
Execution Console
Click "Run & Verify" (or press Ctrl + Enter) to test your code against the challenge.

Keep exploring.

The official references behind this lesson, when you want a second angle or the full details.

One lesson closer.

Reading is a start. Trying it yourself makes the connection.

Solve the hands-on coding challenge in the knowledge section to verify your code and unlock lesson completion.