Python 3.12 Generics: Streamlining Type-Safe Code

Overview

introduces a major syntax overhaul for
Generics
, making it easier than ever to write flexible, reusable code. Generics allow you to parameterize classes and functions with types, ensuring your data structures remain consistent without sacrificing the power of static type checking. Unlike using the any type, which effectively turns off type safety, generics preserve the relationship between inputs and outputs.

Prerequisites

To get the most out of this tutorial, you should have a basic understanding of

class definitions and methods. Familiarity with
Type Annotations
is helpful, as generics are an extension of the broader typing system designed to catch bugs before they reach production.

Key Libraries & Tools

No external libraries are required. You only need the Python 3.12 standard library. Tools like

or integrated development environment (IDE) hover features (like those in
Visual Studio Code
) are essential for seeing these type checks in action.

Code Walkthrough

In previous versions, you had to import and define TypeVar. In 3.12, the syntax is significantly cleaner. We can define a generic Stack by placing the type parameter directly in brackets after the class name.

class Stack[T]:
    def __init__(self) -> None:
        self.items: list[T] = []

    def push(self, item: T) -> None:
        self.items.append(item)

    def pop(self) -> T:
        return self.items.pop()

When you instantiate Stack[int](), the methods automatically adapt. The push method now explicitly expects an integer. This prevents a common headache: accidentally mixing strings into a list intended for numerical calculations.

Constrained Type Parameters

You can restrict what types a generic class accepts by using a tuple of allowed types. This is particularly useful when you need to ensure a class only handles numeric data for operations like summation.

class NumericStack[T: (int, float)](Stack[T]):
    def average(self) -> float:
        return sum(self.items) / len(self.items)

By defining T: (int, float), you tell the type checker that NumericStack[str] is invalid. Note that this is different from a Union. A Union[int, float] creates a stack that can contain a mix of both; a generic T restricted to (int, float) means the stack must be consistently all integers or all floats.

Tips & Gotchas

Always remember that Python remains a dynamically typed language at runtime. While your IDE will highlight a type violation in red if you pass a string to a Stack[int], the

will still execute the code. Type hints are a development-time safety net, not a runtime enforcement mechanism.

Python 3.12 Generics: Streamlining Type-Safe Code

Fancy watching it?

Watch the full video and context

3 min read