Documentation Index
Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt
Use this file to discover all available pages before exploring further.
A generic class in Python is a class that can be parameterized with one or more type variables, allowing it to operate on different data types while maintaining strict static type safety. By defining a class as generic, developers defer the specification of internal types until the class is instantiated, enabling static type checkers (like mypy or pyright) to enforce consistency across attributes, method arguments, and return values.
Syntax and Implementation
Python provides two distinct syntaxes for defining generic classes, depending on the version.
Modern Syntax (Python 3.12+)
Introduced in PEP 695, Python 3.12 natively supports type parameter syntax using square brackets directly in the class definition. This eliminates the need to import TypeVar or Generic.
class Container[T]:
def __init__(self, value: T) -> None:
self.value: T = value
def get_value(self) -> T:
return self.value
Legacy Syntax (Python 3.5 - 3.11)
In older versions, generic classes are constructed by instantiating typing.TypeVar and inheriting from typing.Generic.
from typing import TypeVar, Generic
T = TypeVar('T')
class Container(Generic[T]):
def __init__(self, value: T) -> None:
self.value: T = value
def get_value(self) -> T:
return self.value
Instantiation and Type Binding
When a generic class is instantiated, the type variable is bound to a concrete type. This binding can be explicit (using square brackets during instantiation) or implicit (inferred by the type checker based on the arguments passed to the constructor).
# Explicit type binding
int_container = Container[int](10)
# Implicit type binding (inferred as Container[str])
str_container = Container("text")
# Static type checkers will flag this as an error:
int_container.value = "invalid"
Multiple Type Variables
Generic classes can be parameterized with multiple type variables to represent complex internal structures.
# Python 3.12+
class Pair[K, V]:
def __init__(self, first: K, second: V) -> None:
self.first: K = first
self.second: V = second
# Legacy
K = TypeVar('K')
V = TypeVar('V')
class Pair(Generic[K, V]):
def __init__(self, first: K, second: V) -> None:
self.first: K = first
self.second: V = second
Type Bounds and Constraints
Type variables within generic classes can be restricted to accept only specific types or subclasses of a specific type.
Constrained Types: The type variable must be exactly one of the specified types.
# Python 3.12+
class NumericProcessor[T: (int, float)]:
pass
# Legacy
T_Num = TypeVar('T_Num', int, float)
class NumericProcessor(Generic[T_Num]):
pass
Bound Types: The type variable must be a subclass of a specific class.
class Animal:
pass
# Python 3.12+
class AnimalEnclosure[T: Animal]:
pass
# Legacy
T_Animal = TypeVar('T_Animal', bound=Animal)
class AnimalEnclosure(Generic[T_Animal]):
pass
Variance
Variance dictates how subtyping between generic classes relates to subtyping between their type arguments.
- Invariance:
Container[int] is not a subtype of Container[float]. This is the default behavior.
- Covariance: Subtype relationships are preserved. If
Dog is a subtype of Animal, Producer[Dog] is a subtype of Producer[Animal].
- Contravariance: Subtype relationships are reversed.
Consumer[Animal] is a subtype of Consumer[Dog].
Python 3.12+ Syntax:
Under PEP 695, variance is automatically inferred by static type checkers based on how the type parameter is utilized within the class (e.g., as a return type vs. an argument type).
# Variance is automatically inferred
class Producer[T]:
def produce(self) -> T:
raise NotImplementedError
class Consumer[T]:
def consume(self, item: T) -> None:
raise NotImplementedError
Legacy Syntax:
In older versions, variance must be explicitly declared using the covariant or contravariant keyword arguments in TypeVar.
T_co = TypeVar('T_co', covariant=True)
T_contra = TypeVar('T_contra', contravariant=True)
class Producer(Generic[T_co]):
def produce(self) -> T_co:
raise NotImplementedError
class Consumer(Generic[T_contra]):
def consume(self, item: T_contra) -> None:
raise NotImplementedError
Subclassing Generic Classes
When inheriting from a generic class, the subclass can retain the generic nature, concretize the type variable, or introduce entirely new type variables.
Retaining Generics:
# Python 3.12+
class AdvancedContainer[T](Container[T]):
def process(self) -> T:
raise NotImplementedError
# Legacy
class AdvancedContainer(Container[T]):
def process(self) -> T:
raise NotImplementedError
Concretizing the Type:
# Python 3.12+ and Legacy
class StringContainer(Container[str]):
def __init__(self, value: str) -> None:
super().__init__(value)
Introducing New Type Variables:
A subclass can inherit a concretized type while introducing its own new type variables. In legacy syntax, this requires explicitly inheriting from Generic alongside the concretized base class.
# Python 3.12+
class MappedStringContainer[V](Container[str]):
def __init__(self, key: str, mapped_value: V) -> None:
super().__init__(key)
self.mapped_value: V = mapped_value
# Legacy
V = TypeVar('V')
class MappedStringContainer(Container[str], Generic[V]):
def __init__(self, key: str, mapped_value: V) -> None:
super().__init__(key)
self.mapped_value: V = mapped_value
Master Python with Deep Grasping Methodology!Learn More