← Python Code Modern OOP
Browse Python Concepts

Type Hints in Python — A Practical Guide

Mental Model

Think of Python's type hints as sticky notes attached to your code for a linter to read, but which are torn off before the code actually runs. isinstance() only sees the raw type, like list, not the detail on the "sticky note."

Rule: Never use subscripted generic types inside runtime type inspection functions like isinstance() or issubclass(); use static checkers or a schema validator like Pydantic instead.

The Setup

You are writing an API integration gateway that processes raw JSON payloads. You use modern type hints to signal to your team that a configuration list must only contain integers, and you write a runtime assertion to verify this before committing the data.

What Does This Print?

Broken code
Python
def process_user_ids(payload: dict) -> bool:
    user_ids = payload.get("user_ids")
    # Attempting to validate that user_ids is a list of integers
    if isinstance(user_ids, list[int]):
        return True
    return False

process_user_ids({"user_ids": [1, 2, 3]})
Predict what happens when process_user_ids is executed. Does it validate the array, return False, or crash?

The Output

What actually happens
TypeError: Subscripted generics cannot be used with class and instance checks

This execution crashes immediately at runtime. Python's type checking ecosystem is purely static and managed outside execution by checkers like mypy or pyright. At runtime, generic parameters like list[int] (subscripted generics) do not exist as concrete types. Calling isinstance() with a subscripted generic raises a TypeError because the runtime interpreter does not inspect individual elements within the collection to prove compliance.

Why Python Does This

CPython maintains dynamic type enforcement of basic types but discards detailed generic metadata during evaluation to maintain execution performance. Classes like list are defined in C, and subscripting them (e.g., list[int]) returns a types.GenericAlias object in Python 3.9+. The __instancecheck__ dunder method on GenericAlias is explicitly programmed to raise a TypeError. Checking every element of a list at runtime inside an isinstance call would degrade an $O(1)$ pointer check to an $O(N)$ iteration, violating the speed guarantees of Python's built-in type system.

The Fix

Corrected pattern
Python
def process_user_ids(payload: dict) -> bool:
    user_ids = payload.get("user_ids")
    # First, validate the outer container type
    if not isinstance(user_ids, list):
        return False
    # Explicitly check item types at runtime, or use a tool like Pydantic
    return all(isinstance(uid, int) for uid in user_ids)

The fix involves using a static type checker like mypy for compile-time validation or a runtime schema validator (like Pydantic) which explicitly parses and validates data structures using the type hints. This separates static checks from runtime behavior.

How This Fails in Real Systems

A payments platform checked incoming processing batches using isinstance(batch, list[Transaction]). A quick refactor to modern type hints bypassed static analysis warning checks, causing the application to raise unhandled TypeErrors at the API boundary, rejecting over $40,000 in customer checkouts before the change was reverted.

Key Takeaway

Never use subscripted generic types inside runtime type inspection functions like isinstance() or issubclass(); use static checkers or a schema validator like Pydantic instead.
Common mistake: Developers try to use static type hints for runtime validation or inspection, assuming that subscripted generics like list[int] retain their inner type information at runtime for isinstance() checks.