Reusable checks and exhaustive matches turn 'forgot a case' bugs into checker errors.
Built-in narrowing understands isinstance, is None and == on literals. To narrow with your own function, annotate its return type specially.
`TypeIs[T]` (3.13, PEP 742): def is_str_list(x: object) -> TypeIs[list[str]]. If it returns True, x is narrowed to list[str]; if False, the checker removes that type in the else branch, just like isinstance. The narrowed type must be consistent with the input type.
`TypeGuard[T]` (3.10) is the older form. It narrows only in the true branch and allows narrowing to an unrelated type (like list[object] to list[str]). Prefer TypeIs in new code.
Exhaustiveness: when every member of a union or enum has been handled, the remaining type is Never, the empty type. assert_never(x) accepts only Never, so if someone adds a new member and forgets a branch, the call becomes a checker error. At runtime it raises if reached.
match shape:
case Circle(): ...
case Square(): ...
case _: assert_never(shape)Never (or NoReturn) is also the return type of functions that always raise or exit.
Example
from enum import Enum
from typing import TypeGuard, TypeIs, assert_never
class Status(Enum):
ACTIVE, BANNED = 1, 2
def is_str(x: object) -> TypeIs[str]:
return isinstance(x, str)
def is_str_list(val: list[object]) -> TypeGuard[list[str]]: # list is invariant,
return all(isinstance(x, str) for x in val) # so TypeIs can't be used
def label(s: Status) -> str:
match s:
case Status.ACTIVE: return "ok"
case Status.BANNED: return "blocked"
case _:
assert_never(s) # checker error here if a member is unhandled
def show(x: int | str) -> str:
return x.upper() if is_str(x) else str(x + 1) # else branch: x is int
items: list[object] = ["a", "b"]
if is_str_list(items): print(", ".join(items)) # a, b (items: list[str])
print(show("hi"), show(1), label(Status.BANNED)) # HI 2 blockedRemember
- TypeIs[T] narrows in both branches like isinstance (3.13)
- TypeGuard[T] narrows only when True and allows unrelated types
- assert_never with Never makes the checker enforce exhaustive handling
Watch out for
- Writing a TypeIs function whose runtime check doesn't actually prove the type; the checker trusts it blindly.