Python 3.12 Generics: Streamlining Type-Safe Code
Overview
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
Key Libraries & Tools
No external libraries are required. You only need the Python 3.12 standard library. Tools like
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

Fancy watching it?
Watch the full video and context