AI Skill Report Card

Writing Generic Type Annotations

A-84·Aug 12, 2026·Source: Web
14 / 15
Python
# Modern (Python 3.9+): use built-in collection types directly def process(items: list[int]) -> dict[str, int]: ... # Modern (Python 3.10+): use | for unions instead of typing.Union/Optional def find(key: str) -> int | None: ... # Avoid legacy forms unless supporting Python < 3.9 / < 3.10 from typing import List, Dict, Union, Optional # legacy, avoid in new code
Recommendation
Add an example showing a mixed-style refactor (before/after full function with multiple annotations) to demonstrate consistency application more concretely
13 / 15

Progress:

  • Step 1: Determine minimum supported Python version for the codebase
  • Step 2: Choose generic alias style (built-in vs typing module) based on version
  • Step 3: Choose union style (| vs typing.Union/Optional) based on version
  • Step 4: Apply consistently across the file/module
  • Step 5: Verify with a type checker (mypy/pyright) since these are compile-time-only checks

Step 1 — Version check:

  • Python >= 3.9: built-in generics (list[int], dict[str, int], tuple[int, ...], set[str]) work at runtime.
  • Python >= 3.10: X | Y union syntax works at runtime (isinstance checks, int | None).
  • Python < 3.9: must use typing.List, typing.Dict, etc.
  • Python < 3.10 but >= 3.7 with from __future__ import annotations: can use both new syntaxes in annotations (they're stored as strings, not evaluated), but not in runtime contexts like isinstance(x, int | str).

Step 2 — Generic alias mapping:

Legacy (typing)Modern (built-in)
List[int]list[int]
Dict[str, int]dict[str, int]
Tuple[int, str]tuple[int, str]
Set[int]set[int]
FrozenSet[int]frozenset[int]
Type[C]type[C]

Step 3 — Union mapping:

LegacyModern
Union[int, str]int | str
Optional[int]int | None
Union[int, str, None]int | str | None

Step 4 — Apply consistently. Don't mix List[int] and dict[str, int] in the same file — pick one style per codebase, driven by the minimum supported version.

Step 5 — Verify. Built-in generics and | unions are purely typing-time constructs; run mypy or pyright to catch mistakes, since Python itself won't validate annotation contents at runtime beyond syntax.

Recommendation
Consider a table or note on TypeAlias/type statement (PEP 695, 3.12+) since the skill already targets modern Python versions
16 / 20

Example 1: Input: def get_user(id: int) -> Optional[User]: in a Python 3.11 codebase Output: def get_user(id: int) -> User | None:

Example 2: Input: def merge(a: Dict[str, List[int]], b: Dict[str, List[int]]) -> Dict[str, List[int]]: targeting Python 3.9+ Output: def merge(a: dict[str, list[int]], b: dict[str, list[int]]) -> dict[str, list[int]]:

Example 3: Input: A library must support Python 3.8 as minimum Output: Keep from typing import List, Dict, Optional, Union and legacy syntax throughout; do not introduce list[int] or X | Y since they'll raise TypeError at runtime on 3.8.

Example 4: Input: isinstance(value, Union[int, float]) — runtime isinstance check Output: isinstance(value, (int, float)) — note that Union/| are not valid as the second argument to isinstance; use a tuple instead. X | Y unions in types.UnionType form (Python 3.10+) do work with isinstance(x, int | float), but typing.Union[...] never does.

Recommendation
The workflow checklist and the detailed step explanations are somewhat redundant with the tables below—could tighten by merging checklist directly into the mapping tables
  • Prefer built-in generic aliases (list, dict, tuple, set, type) over typing equivalents when the minimum Python version allows it — they're simpler and require no import.
  • Prefer X | Y over Optional[X]/Union[X, Y] on Python 3.10+.
  • Use from __future__ import annotations to unlock modern syntax in annotations on older interpreters (3.7+), remembering this only affects annotations, not runtime type objects.
  • For generic classes you define, subscript with built-ins too, e.g. class Stack(list[T]): ....
  • Use X | Y for isinstance/issubclass checks only when targeting 3.10+; otherwise use a tuple (X, Y).
  • Keep annotation style consistent within a project; document the chosen minimum version and style in contribution guidelines.
  • Using list[int] or X | Y at runtime (not just in annotations) on Python < 3.9/3.10 — causes TypeError.
  • Mixing legacy and modern styles inconsistently within the same module.
  • Passing typing.Union[...] to isinstance/issubclass — it's not supported; use a tuple or a 3.10+ X | Y union instead.
  • Forgetting that from __future__ import annotations makes all annotations strings (not evaluated), which can break tools that need real runtime type objects (e.g., some uses of typing.get_type_hints, dataclasses relying on runtime introspection without resolution).
  • Assuming built-in generics support all typing features — some constructs (Callable, TypeVar bounds, Protocol, Literal, TypedDict) still require importing from typing.
0
Grade A-AI Skill Framework
Scorecard
Criteria Breakdown
Quick Start
14/15
Workflow
13/15
Examples
16/20
Completeness
18/20
Format
15/15
Conciseness
14/15