CodeNebula
Python🚀Advancedadvanced

Type Narrowing and `TypeGuard`

Python · Advanced · Run with python3

Explanation

Type narrowing is the process by which a type checker infers a more specific type after a check. `isinstance(x, str)` narrows `x: int | str` to `str` in the `if` branch. `TypeGuard[T]` (PEP 647, Python 3.10+) lets you write custom narrowing functions: a function returning `TypeGuard[SpecificType]` tells the type checker that the return value implies the argument is of `SpecificType`. Python 3.12 added `TypeIs` (PEP 742) as a stricter alternative.

Real-World Scenario

A function receives `data: dict[str, object]` from JSON. After validating with `is_valid_config(data)` (returning `TypeGuard[Config]`), the type checker knows `data` is `Config` — no `cast()` needed, and you get full autocomplete on `Config` attributes. ```python from typing import TypeGuard, Union from dataclasses import dataclass @dataclass class Cat: name: str indoor: bool @dataclass class Dog: name: str breed: str Animal = Union[Cat, Dog] def is_cat(animal: Animal) -> TypeGuard[Cat]: return isinstance(animal, Cat) def describe(animal: Animal) -> str: if is_cat(animal): # Type checker knows animal is Cat here return f"Cat {animal.name}, indoor={animal.indoor}" else: # Type checker knows animal is Dog here return f"Dog {animal.name}, breed={animal.breed}" animals: list[Animal] = [Cat("Whiskers", True), Dog("Rex", "Labrador")] for a in animals: print(describe(a)) ```

Quick Snippet

snippet.py
from typing import TypeGuard, Union
from dataclasses import dataclass

@dataclass
class Cat:
    name: str
    indoor: bool

@dataclass
class Dog:
    name: str
    breed: str

Animal = Union[Cat, Dog]

def is_cat(animal: Animal) -> TypeGuard[Cat]:
    return isinstance(animal, Cat)

def describe(animal: Animal) -> str:
    if is_cat(animal):
        # Type checker knows animal is Cat here
        return f"Cat {animal.name}, indoor={animal.indoor}"
    else:
        # Type checker knows animal is Dog here
        return f"Dog {animal.name}, breed={animal.breed}"

animals: list[Animal] = [Cat("Whiskers", True), Dog("Rex", "Labrador")]
for a in animals:
    print(describe(a))

Full Example

type_narrowing.py
# Compile: python3 type_narrowing.py
# Output:
# Cat Whiskers, indoor=True
# Dog Rex, breed=Labrador

from typing import TypeGuard, Union
from dataclasses import dataclass

@dataclass
class Cat:
    name: str
    indoor: bool

@dataclass
class Dog:
    name: str
    breed: str

Animal = Union[Cat, Dog]

def is_cat(animal: Animal) -> TypeGuard[Cat]:
    return isinstance(animal, Cat)

def describe(animal: Animal) -> str:
    if is_cat(animal):
        return f"Cat {animal.name}, indoor={animal.indoor}"
    else:
        return f"Dog {animal.name}, breed={animal.breed}"

animals: list[Animal] = [Cat("Whiskers", True), Dog("Rex", "Labrador")]
for a in animals:
    print(describe(a))

# isinstance narrowing (built-in, no TypeGuard needed)
def process(value: int | str | list) -> str:
    if isinstance(value, int):
        return f"int: {value * 2}"
    elif isinstance(value, str):
        return f"str: {value.upper()}"
    else:
        return f"list: {len(value)} items"

print(process(21))
print(process("hello"))
print(process([1, 2, 3]))
Run command
python3 type_narrowing.py