How to use
- Copy the installation command and review the source package before installing it in your own agent environment.
- Check the source document for required tools, credentials and supported hosts.
- Describe your input and desired deliverable; review the output against the source material.
npx skills add https://github.com/bateau84/skills --skill python-type-checkingBefore you start
- Installation does not include a model subscription, API allowance or permission to access your files.
- Repository stars and installation counts describe activity, not an independent quality or security test.
Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.
Pair withpython-common-practice,python-pydantic(runtime validation), andpython-lintas relevant.
Persona: You are a Python engineer who uses types as machine-checked documentation. A correct annotation prevents a class of bugs without a single test.
Static Typing
Type hints are checked by a separate tool, not the interpreter — Python ignores them at runtime. Their value is catching mismatches before execution and documenting intent precisely. Annotate public function signatures and module-level data; let inference handle obvious locals.
Run one static checker in the inner check loop (→ See python-common-practice): mypy (mature, ubiquitous), ty (Astral, very fast, pairs with a ruff/uv toolchain), or pyright (strict, editor-native). Pick one per repo and run it in CI; they disagree at the edges, so don't mix verdicts.
Annotate signatures, infer locals
def fuzzy_match(a: str, b: str, threshold: int = 80) -> bool:
score = ratio(a, b) # inferred int — no annotation needed
return score >= thresholdUse modern syntax (target a recent requires-python):
list[str],dict[str, int],tuple[int, ...]— builtins, nottyping.List.X | YandX | None— notUnion[X, Y]/Optional[X].from collections.abc import Iterable, Callable, Sequence— not the deprecatedtypingaliases.
None is the most common bug — model it
X | None forces callers to handle absence. The checker then proves you narrowed it before use — this is where typing pays for itself.
def find_user(uid: int) -> User | None: ...
u = find_user(1)
print(u.name) # ✗ checker error: u may be None
if u is not None:
print(u.name) # ✓ narrowed to User inside the guardPrefer narrowing (if x is None: return) over cast or assert. A cast silences the checker without proving anything; reserve it for cases the checker genuinely can't follow, and comment why.
Protocols — structural typing over inheritance
A Protocol matches any object with the right shape, so you depend on a capability, not a base class. This is the typed version of duck typing and keeps modules decoupled (no shared base import).
from typing import Protocol
class MatchRepository(Protocol):
def save(self, decision: MatchDecision) -> None: ...
def by_video(self, video_id: str) -> MatchDecision | None: ...
def record(repo: MatchRepository, d: MatchDecision) -> None:
repo.save(d) # any class with these methods satisfies the typeUse Protocol for ports/interfaces (the SQLite repo, an API client) so tests can pass a lightweight fake that simply has the methods.
Other high-value tools
Literal["allow", "deny"]— a closed set of string values, checked at call sites.TypedDict— a dict with known string keys and per-key types (for JSON payloads you don't model with Pydantic).@overload— different return types for different argument shapes.typing.Final— a constant that must not be reassigned.
Gradual typing
You don't have to type everything at once. Start strict on new modules; let legacy modules stay loose. In mypy, ratchet up with disallow_untyped_defs per-package rather than globally on day one — an all-or-nothing flip drowns you in errors and teaches nothing.
[tool.mypy]
python_version = "3.13"
warn_unused_ignores = true
warn_redundant_casts = true
# strict = true # turn on per-package as coverage growsCommon mistakes
| Mistake | Fix | ||
|---|---|---|---|
Optional[X] / Union[A, B] / List[str] | Use `X | None, A | B, list[str]`. |
cast to silence a None error | Narrow with an is None guard; reserve cast for genuinely opaque cases, with a comment. | ||
Any to make an error disappear | Any disables checking for that value; use a precise type or object + narrowing. | ||
| Inheriting a base class just for typing | Define a Protocol; depend on shape, not lineage. | ||
Bare # type: ignore | Pin the error code: # type: ignore[arg-type]; warn_unused_ignores cleans stale ones. | ||
| Type checker not in CI | Add it to the inner loop and CI, or annotations silently rot. |
Diagnose: 1- mypy . / ty check / pyright — the type checker is itself the diagnostic; read its output before "fixing" by adding Any 2- reveal_type(x) in a checked file — prints the inferred type so you see what the checker sees.



