Generic Classes with class Stack[T]

Write containers that stay type-safe for any element type using Python 3.12+ generic syntax.

A generic class works with any type while keeping the type checker informed of exactly which one you chose. A Stack of int and a Stack of str share one implementation but never get confused for each other.

The modern syntax (Python 3.12+)

Since Python 3.12, you declare a type parameter right in the class header — no more importing TypeVar and inheriting from Generic:

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()

numbers: Stack[int] = Stack()
numbers.push(1)      # a type checker now knows pop() returns int

Why it matters

At runtime the type parameter is essentially erased — your code runs the same. The payoff is at authoring time: your editor autocompletes correctly, and a checker like mypy flags numbers.push('oops') before you ever run the program. Generics are how you get the flexibility of duck typing with the safety net of static types.

Example

Example · python
# Python 3.12+ inline generics — no typing imports needed
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:
        if not self._items:
            raise IndexError('pop from empty stack')
        return self._items.pop()

    def peek(self) -> T:
        return self._items[-1]

    def __len__(self) -> int:
        return len(self._items)

# A generic FUNCTION too, with a bound: only orderable types
def largest[T: (int, float, str)](items: list[T]) -> T:
    winner = items[0]
    for x in items[1:]:
        if x > winner:
            winner = x
    return winner

s: Stack[str] = Stack()
s.push('alpha'); s.push('beta')
print('peek :', s.peek())        # beta
print('pop  :', s.pop())         # beta
print('size :', len(s))          # 1

print('max int :', largest([3, 9, 2, 7]))       # 9
print('max str :', largest(['pear', 'fig', 'kiwi']))  # pear
# A checker would reject:  s.push(123)  and  largest([1, 'x'])

# Output:
# peek : beta
# pop  : beta
# size : 1
# max int : 9
# max str : pear

When to use it

  • A typed Stack[int] ensures only integers are pushed, catching misuse at static analysis time.
  • A generic Result[T] class wraps a success value or an error message while preserving the value type.
  • A typed Queue[Task] makes it clear what objects can be enqueued and dequeued in a worker pool.

More examples

Generic Stack with class[T]

Defines a generic Stack using Python 3.12+ class[T] syntax that preserves the element type.

Example · python
class Stack[T]:
    def __init__(self): self._items: list[T] = []
    def push(self, item: T) -> None: self._items.append(item)
    def pop(self) -> T: return self._items.pop()
    def __len__(self) -> int: return len(self._items)

s: Stack[int] = Stack()
s.push(1)
s.push(2)
print(s.pop())   # 2

Generic Pair class

Uses two type parameters A and B to create a strongly-typed pair with a type-aware swap method.

Example · python
class Pair[A, B]:
    def __init__(self, first: A, second: B):
        self.first = first
        self.second = second
    def swap(self) -> 'Pair[B, A]':
        return Pair(self.second, self.first)

p = Pair('hello', 42)
print(p.first, p.second)    # hello 42
swapped = p.swap()
print(swapped.first)         # 42

Generic class with TypeVar (3.11 compat)

Provides the pre-3.12 approach using TypeVar and Generic[T] for backwards-compatible generic classes.

Example · python
from typing import Generic, TypeVar

T = TypeVar('T')

class Box(Generic[T]):
    def __init__(self, value: T): self.value = value
    def unwrap(self) -> T: return self.value

box: Box[str] = Box('treasure')
print(box.unwrap())   # treasure

Discussion

  • Be the first to comment on this lesson.