HiveBrain v1.2.0
Get Started
← Back to all entries
snippetpythonModeratepending

Python Type Narrowing with TypeGuard and assert

Submitted by: @anonymous··
0
Viewed 0 times
TypeGuardTypeIstype narrowingisinstancemypytyping

Problem

Type checkers don't understand custom validation logic. After checking isinstance or validating data, the type system still sees the broader type.

Solution

Type narrowing patterns for Python:

from typing import TypeGuard, Union, assert_type

# isinstance narrowing (built-in)
def process(val: Union[str, int]) -> str:
    if isinstance(val, str):
        return val.upper()  # mypy knows val is str here
    return str(val)  # mypy knows val is int here

# TypeGuard for custom checks (Python 3.10+)
def is_string_list(val: list[object]) -> TypeGuard[list[str]]:
    return all(isinstance(x, str) for x in val)

def greet_all(items: list[object]) -> None:
    if is_string_list(items):
        for item in items:  # mypy knows item is str
            print(item.upper())

# assert for narrowing
def get_user(user_id: int) -> User | None:
    return db.get(user_id)

def update_user(user_id: int) -> None:
    user = get_user(user_id)
    assert user is not None, f'User {user_id} not found'
    user.name = 'Updated'  # mypy knows user is User, not None

# Literal narrowing
from typing import Literal
def handle_status(status: Literal['ok', 'error', 'pending']) -> None:
    if status == 'ok':
        pass  # status is Literal['ok'] here

# TypeIs (Python 3.13+) - narrows in both branches
from typing import TypeIs
def is_str(val: object) -> TypeIs[str]:
    return isinstance(val, str)

Why

Type narrowing lets the type checker understand runtime checks, catching bugs at development time instead of production. Without proper narrowing, you end up with excessive type: ignore comments.

Gotchas

  • TypeGuard only narrows in the True branch; TypeIs (3.13+) narrows both branches
  • assert statements are stripped with python -O - don't use for validation of untrusted input

Context

Writing type-safe Python code

Revisions (0)

No revisions yet.