Complete guide to Python's __class_getitem__ method covering type hints, generics, and custom container types.
Last modified April 8, 2025
This comprehensive guide explores Python’s class_getitem method, introduced in PEP 560 for type hinting and generic class support. We’ll cover basic usage, type hints, generics, and practical examples.
The class_getitem method allows classes to support subscription notation (square brackets) for type hinting purposes. It’s called when using Class[item] syntax.
Key characteristics: it’s a class method (though not decorated as such), receives the class as first argument, and returns a specialized version of the class. It enables generic type annotations without runtime type checking.
Here’s a simple implementation showing how class_getitem works. This demonstrates the basic syntax and behavior of the method.
basic_class_getitem.py
class GenericBox: def class_getitem(cls, item): print(f"Creating GenericBox specialized for {item}") return f"GenericBox[{item.name}]"
print(GenericBox[int]) # GenericBox[int] print(GenericBox[str]) # GenericBox[str]
This example shows how class_getitem intercepts the square bracket notation. When GenericBox[int] is called, it invokes class_getitem with int as the item.
The method returns a string here for demonstration, but in real usage it would typically return a specialized version of the class or a typing._GenericAlias.
class_getitem is primarily used to support type hints in Python. Here’s how to implement it for custom container types.
type_hinting.py
from typing import Any, TypeVar, Generic
T = TypeVar(‘T’)
class Box(Generic[T]): def init(self, content: T): self.content = content
@classmethod
def __class_getitem__(cls, item):
return super().__class_getitem__(item)
def __repr__(self):
return f"Box({self.content!r})"
def process_box(box: Box[int]) -> int: return box.content * 2
box = Box(42) print(process_box(box))
This example shows a generic Box class that can be parameterized with types. The class_getitem implementation delegates to the parent Generic class to handle type parameterization correctly.
The type checker will understand Box[int] as a box containing integers, while at runtime it returns a typing._GenericAlias instance.
You can create your own generic classes without inheriting from typing.Generic by implementing class_getitem.
custom_generic.py
class Pair: def init(self, first, second): self.first = first self.second = second
def __class_getitem__(cls, items):
if not isinstance(items, tuple):
items = (items,)
if len(items) != 2:
raise TypeError("Pair requires exactly two type arguments")
first_type, second_type = items
return type(f"Pair[{first_type.__name__}, {second_type.__name__}]",
(cls,),
{'__annotations__': {'first': first_type, 'second': second_type}})
def __repr__(self):
return f"Pair({self.first!r}, {self.second!r})"
IntStrPair = Pair[int, str] pair = IntStrPair(42, “answer”) print(pair) # Pair(42, ‘answer’)
This custom Pair class implements its own generic type support. The class_getitem method creates a new subclass with type annotations based on the provided type arguments.
At runtime, Pair[int, str] creates a new class with appropriate type annotations, which type checkers can use for static type checking.
While class_getitem is mainly for type hints, you can combine it with runtime type checking for more robust code.
runtime_checking.py
class CheckedList: def init(self, items): self.items = list(items)
def __class_getitem__(cls, item_type):
class CheckedListSubclass(cls):
def append(self, item):
if not isinstance(item, item_type):
raise TypeError(f"Expected {item_type.__name__}, got {type(item).__name__}")
super().append(item)
return CheckedListSubclass
def append(self, item):
self.items.append(item)
IntList = CheckedList[int] numbers = IntList([1, 2, 3]) numbers.append(4)
This example creates a type-checked list that verifies items match the specified type at runtime. The class_getitem method creates a subclass with runtime type checking.
When CheckedList[int] is called, it returns a subclass that validates all appended items are integers. This combines static type hints with runtime validation.
class_getitem can handle forward references using string literals, which is useful for type hints that reference not-yet-defined types.
forward_refs.py
class Node: def class_getitem(cls, item): if isinstance(item, str): # Handle forward references return f"Node[’{item}’]" return f"Node[{item.name}]"
class Tree: pass
print(Node[“Tree”]) # Node[‘Tree’]
print(Node[Tree]) # Node[Tree]
This example demonstrates how class_getitem can handle both actual types and string literals. String literals are used for forward references in type hints.
Type checkers will recognize this pattern and properly handle forward references in type annotations, while at runtime it just returns a formatted string.
Type hints first: Main purpose is type hinting, not runtime behavior
Consistent returns: Return typing._GenericAlias for compatibility
Forward references: Support string literals for forward references
Document behavior: Clearly document any special type handling
Performance: Cache created generic types if performance is critical
My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.
List all Python tutorials.