Generic Types
Overview
Generic types allow you to specify the *contents* of a container type. Instead of just saying a variable is a `list`, you say it is a `list[str]`. users: list[str] = ['alice', 'bob'] scores: dict[str, int] = {'alice': 95} For dictionaries, you specify both key and value types: `dict[str, int]`. In older Python (versions before 3.9), you had to import `List` and `Dict` from the `typing` module, but modern Python allows using the built-in lowercase types directly. def get_first(items: list) -> typing.Any: return items[0] result = get_first([1, 2, 3]) Using plain `list` in function signatures causes type erasure. To retain the inner type (e.g., returning an `int` when passing a `list[int]`), you need type variables. This is covered fully in typevar-generic-functions.
A bare `list` or `dict` annotation doesn't tell the IDE (Integrated Development Environment) or type checker what methods are safe to call on the items inside the container. Generics provide that inner visibility.
Where used: Defining nested JSON payloads in APIs, ORM query results (e.g. a `list` of models)
Why learn this
- Enabling autocomplete for elements inside `for` loops.
- Documenting complex nested data structures like `list[dict[str, int]]`.
Code walkthrough
def summarize_scores(scores: dict[str, list[int]]) -> dict[str, int]:
result = {}
for name, num_list in scores.items():
result[name] = sum(num_list)
return result
print(summarize_scores({'Alice': [10, 20]}))
Focus: def summarize_scores(scores: dict[str, list[int]]) -> dict[str, int]:
Aha moment
my_list: list[int] = [1, 2, 3]
my_list.append('hello')
print(my_list)
Prediction: What does this code print?
Common guess: It raises a `TypeError` because we appended a `str` to a `list[int]`.
It prints `[1, 2, 3, 'hello']`. At runtime, Python ignores the `[int]` part entirely. The `list` is just a standard dynamic list.
Common mistakes
- Using typing.List in modern Python: This import is deprecated for simple annotations in Python 3.9+. Use the built-in `list` and `dict` directly instead.
- Assuming lists are homogeneous at runtime: Annotating a `list` as `list[int]` does not stop you from appending a `str` to it at runtime. It only flags a warning in static type checkers.
Glossary
- static type checkers
- Tools that analyze your code without running it to find potential errors related to data types. Example: running `mypy script.py` would flag `x: int = 'hello'` as a type error without executing the code.
- deprecated
- A feature or practice that is no longer recommended and may be removed in future versions. Example: `from typing import List` is deprecated in Python 3.9+ — use the built-in `list` instead.
Recall questions
- How do you annotate a `list` containing dictionaries, where the dictionaries have `str` keys and `bool` values?
- Why is `list[str]` preferred over importing `List` from the `typing` module?
- What happens to the IDE autocomplete when you use a plain `list` annotation in a function signature instead of a generic type like `list[str]`?
Understanding checks
Why will a static type checker complain about this function?
The function claims to return a `list` of strings (`list[str]`), but the returned `list` contains an `int` (`42`).
Generic annotations enforce that all elements within the container match the specified inner type when analyzed by a type checker.
If you have a variable typed as `data: dict[str, int | str]`, what kinds of values are allowed in the dictionary?
The dictionary keys must be strings (`str`), and the values can be either integers (`int`) or strings (`str`).
Generic types can be composed with other type hints like Unions (`|`) to express complex allowed shapes for the container's contents.
Practice tasks
Add generic type hints
Given this working code, add generic type hints. `users` is a `list` of strings (`str`), and `roles` is a `list` of strings (`str`). The function returns a `dict` mapping a `str` to a `str`.
Challenge
Type a nested data structure
Write a function `flatten_groups` that takes a `dict` where keys are strings (group names) and values are lists of strings (member names). It should return a single flat `list` of all member names (`str`). Provide full type hints.
Continue learning
Previous: Basic Annotations
Previous: Optional and Union Types