Techniques for narrowing types from broad to specific in mypy
Type narrowing is when you convince the type checker that a broader type is actually more specific. For instance, an object of type Shape might actually be of the narrower type Square.
def process(obj: object) -> None: if isinstance(obj, int): # Type is narrowed to int within this branch reveal_type(obj) # Revealed type: "builtins.int" print(obj + 1) # OK
Create custom type narrowing functions using TypeGuard:
from typing import TypeGuarddef is_str_list(val: list[object]) -> TypeGuard[list[str]]: """Check if all elements are strings.""" return all(isinstance(x, str) for x in val)def process(items: list[object]) -> None: if is_str_list(items): # items is narrowed to list[str] for item in items: print(item.upper()) # OK
from typing import TypeGuardclass User: name: strclass Admin(User): privileges: list[str]def is_admin(user: User) -> TypeGuard[Admin]: return isinstance(user, Admin)def process_user(user: User) -> None: if is_admin(user): # user is narrowed to Admin print(user.privileges) # OK else: print(user.name) # Still User
from typing import Literaldef process(mode: str) -> None: if mode in ("read", "write"): # mode is narrowed to Literal["read", "write"] reveal_type(mode) # Literal["read", "write"]
def process(value: int | str | list) -> None: match value: case int(x): reveal_type(x) # int case str(s): reveal_type(s) # str case [*items]: reveal_type(items) # list[int | str | list[Any]]
from typing import Literal, Unionfrom dataclasses import dataclass@dataclassclass Success: kind: Literal["success"] value: int@dataclassclass Error: kind: Literal["error"] message: strResult = Union[Success, Error]def process(result: Result) -> None: if result.kind == "success": # result is narrowed to Success print(result.value) # OK else: # result is narrowed to Error print(result.message) # OK