Basic Annotations
Overview
Type hints explicitly declare the expected data types of variables, arguments, and return values. They serve as a mental model for developers and static analysis tools, rather than strict rules for the Python interpreter. Variables are like boxes; type hints are just labels describing what should go in the box, but the box doesn't actually stop you from putting something else inside. def greet(name: str) -> str: return f"Hello, {name}" Crucially, Python ignores these annotations at runtime. If you pass an `int` to a parameter hinted as a `str`, the code will still execute unless an external tool like `mypy` flags the mismatch. def set_username(name: str) -> None: pass set_username(None) # Static tools will flag this error! A common trap is passing `None` to a parameter hinted with a basic type like `str`. While Python allows this at runtime, static analysis tools will flag it as an error because `None` is not a `str`. This scenario is covered fully in `optional-union-types`.
Type hints drastically improve developer experience by providing three major benefits: - Enabling IDE autocomplete to speed up development. - Catching type-related bugs before the code ever runs. - Self-documenting APIs, making functions easier to read and use.
Where used: FastAPI, Pydantic, Static analysis (mypy)
Why learn this
- Catching bugs like adding an `int` to a `str` before running the code.
- Enabling powerful framework features like `FastAPI`'s automatic request validation.
- Improving code readability and maintainability for large projects.
Code walkthrough
def process_user(username: str, tags: list[str]) -> dict[str, bool]:
is_admin = 'admin' in tags
return {username: is_admin}
result = process_user('alice', ['user', 'admin'])
print(result)
Focus: def process_user(username: str, tags: list[str]) -> dict[str, bool]:
Aha moment
age: int = 'twenty'
print(type(age))
Prediction: What does `type(age)` print?
Common guess: `<class 'int'>` or it raises a `TypeError`.
It prints `<class 'str'>`. The `: int` annotation is completely ignored at runtime, so `age` just binds to the `str` object.
Common mistakes
- Assuming runtime enforcement: Type hints do not enforce types at runtime by default. A function defined as `def add(x: int):` can still be called with `add('hello')` unless validated by an external tool.
- Using `list` instead of `list[int]`: A bare `list` hint is too broad and means a list of anything. To be helpful, specify the inner type like `list[str]` or `dict[str, int]`.
Glossary
- static analysis tools
- Programs that analyze your code without actually running it to find bugs, stylistics errors, or incorrect data types. Example: `mypy app.py`.
- autocomplete
- A feature in code editors that predicts and suggests what you are going to type next, speeding up development. Example: typing `user.` suggests `name`.
Recall questions
- Do type hints stop you from passing the wrong type at runtime?
- How do you specify that a `dict` should have `str` keys and `int` values?
- What happens if you pass `None` to a parameter type hinted as `str` during static analysis?
Understanding checks
What happens when this code is executed?
It prints '33'.
Python ignores the `int` type hint at runtime. The function receives the string `'3'`, and multiplying a `str` by `2` duplicates it.
If type hints aren't enforced at runtime, why do frameworks like `FastAPI` use them so heavily?
`FastAPI` introspects the type hints at startup to automatically build runtime validation and documentation.
Type hints provide metadata that clever libraries can read using Python's `typing.get_type_hints()` to build powerful features.
Practice tasks
Add basic type hints
Given this working code, change it so that the arguments and return value are fully typed. `items` is a `list` of `str`, and `price_map` is a `dict` with `str` keys and `float` values. The function returns a `float`.
Challenge
Type a configuration merger
Write a function `merge_configs` that takes two dictionaries with `str` keys and `str` values. It should return a new `dict` of the same type containing all keys from both, with the second dictionary's values overriding the first's on conflicts.
Continue learning
Previous: Primitive types: int, float, bool, str
Previous: Defining functions and return values
Previous: Lists: indexing, slicing, methods
Previous: Dictionaries: operations and use cases
Next: Optional and Union Types
Next: Generic Types