🌐
Python
typing.python.org › en › latest › reference › generics.html
Generics — typing documentation
You may have seen type hints like list[str] or dict[str, int] in Python code. These types are interesting in that they are parametrised by other types! A list[str] isn’t just a list, it’s a list of strings. Types with type parameters like this are called generic types.
🌐
Mypy
mypy.readthedocs.io › en › stable › generics.html
Generics - mypy 1.19.1 documentation
Here is the same example using the old syntax (required for Python 3.11 and earlier, but also supported on newer Python versions): from typing import TypeVar, Generic T = TypeVar('T') # Define type variable "T" class Stack(Generic[T]): def __init__(self) -> None: # Create an empty list with items of type T self.items: list[T] = [] def push(self, item: T) -> None: self.items.append(item) def pop(self) -> T: return self.items.pop() def empty(self) -> bool: return not self.items
🌐
Python documentation
docs.python.org › 3 › library › typing.html
typing — Support for type hints — Python 3.14.3 documentation
1 month ago - Their intended purpose is to be building blocks for creating generic types and type aliases. These objects can be created through special syntax (type parameter lists and the type statement). For compatibility with Python 3.11 and earlier, they can also be created without the dedicated syntax, as documented below.
🌐
Tutorialspoint
tutorialspoint.com › python › python_generics.htm
Python - Generics
In Python, generics is a mechanism with which you to define functions, classes, or methods that can operate on multiple types while maintaining type safety. With the implementation of Generics enable it is possible to write reusable code that ...
🌐
GeeksforGeeks
geeksforgeeks.org › python › python-generics
Python Generics - GeeksforGeeks
October 29, 2025 - So, normally, "generic" functions would just verify if the parameters or objects had the bare minimum of required properties and then handle the data accordingly. Example : In below code, Python generics enable type constraints for functions like process_data, ensuring that inputs conform to expected types; meanwhile, duck typing allows objects to be treated as the required type based on their behavior, irrespective of their explicit type, as demonstrated in process_data's dynamic handling of objects with a quack method.
🌐
Real Python
realpython.com › ref › glossary › generic-type
generic type | Python Glossary – Real Python
The type parameter T in the class header makes Stack a generic class, and you use list[T] to store items of that type. The Stack supports pushing items of type T onto the stack and popping them off, enforcing type safety. For example, Stack[int]() creates a stack for integers, while Stack[str]() creates a stack for strings, and both enforce consistent typing for their operations. ... In this guide, you'll look at Python type checking.
🌐
GitHub
github.com › python › mypy › blob › master › docs › source › generics.rst
mypy/docs/source/generics.rst at master · python/mypy
September 7, 2022 - Here is the same example using the old syntax (required for Python 3.11 and earlier, but also supported on newer Python versions): from typing import TypeVar, Generic T = TypeVar('T') # Define type variable "T" class Stack(Generic[T]): def __init__(self) -> None: # Create an empty list with items of type T self.items: list[T] = [] def push(self, item: T) -> None: self.items.append(item) def pop(self) -> T: return self.items.pop() def empty(self) -> bool: return not self.items
Author   python
🌐
Medium
medium.com › @sunilnepali844 › the-complete-guide-to-python-generics-from-beginner-to-pro-1d11d19b474c
The Complete Guide to Python Generics: From Beginner to Pro | by Sunil Nepali | Medium
August 26, 2025 - Generics allow you to define classes, functions, and data structures that can work with any type, but still let you specify which type will be used when the class/function is instantiated.
🌐
pytz
pythonhosted.org › generic
generic v0.2 documentation
The example from the previous section demonstrates basic usage of generic.multidispatch for defining multifunctions with single argument.
Find elsewhere
Top answer
1 of 10
205

The other answers are totally fine:

  • One does not need a special syntax to support generics in Python
  • Python uses duck typing as pointed out by André.

However, if you still want a typed variant, there is a built-in solution since Python 3.5.

A full list of available type annotations is available in the Python documentation.


Generic classes:

from typing import TypeVar, Generic, List

T = TypeVar('T')

class Stack(Generic[T]):
    def __init__(self) -> None:
        # Create an empty list with items of type T
        self.items: List[T] = []

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

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

    def empty(self) -> bool:
        return not self.items
# Construct an empty Stack[int] instance
stack = Stack[int]()
stack.push(2)
stack.pop()
stack.push('x')        # Type error

Generic functions:

from typing import TypeVar, Sequence

T = TypeVar('T')      # Declare type variable

def first(seq: Sequence[T]) -> T:
    return seq[0]

def last(seq: Sequence[T]) -> T:
    return seq[-1]


n = first([1, 2, 3])  # n has type int.

Static type checking:

You must use a static type checker such as mypy or Pyre (developed by Meta/FB) to analyze your source code.

Install mypy:

python3 -m pip install mypy

Analyze your source code, for example a certain file:

mypy foo.py

or directory:

mypy some_directory

mypy will detect and print type errors. A concrete output for the Stack example provided above:

foo.py:23: error: Argument 1 to "push" of "Stack" has incompatible type "str"; expected "int"

References: mypy documentation about generics and running mypy

2 of 10
116

Python uses duck typing, so it doesn't need special syntax to handle multiple types.

If you're from a C++ background, you'll remember that, as long as the operations used in the template function/class are defined on some type T (at the syntax level), you can use that type T in the template.

So, basically, it works the same way:

  1. define a contract for the type of items you want to insert in the binary tree.
  2. document this contract (i.e. in the class documentation)
  3. implement the binary tree using only operations specified in the contract
  4. enjoy

You'll note however, that unless you write explicit type checking (which is usually discouraged), you won't be able to enforce that a binary tree contains only elements of the chosen type.

🌐
ArjanCodes
arjancodes.com › blog › python-generics-tutorial
How to Use Python Generics Effectively | ArjanCodes
May 30, 2024 - Understand Python generics with this detailed tutorial. Improve your coding with type hints, reusable functions, and practical examples.
🌐
Medium
medium.com › @steveYeah › using-generics-in-python-99010e5056eb
Using Generics in Python. If you are using type hints in Python… | by SteveYeah | Medium
July 21, 2021 - While Any itself is a very convenient ... to the reader. This is where generics come in. In this article I go over the basics of generics and how they can be used in Python to better document and communicate the intentions of your code, and in some cases even enforce correct...
Top answer
1 of 1
5

Type checking vs runtime

After writing this, I finally understood @Alexander point in first comment: whatever you write in annotations, it does not affect runtime, and your code is executed in the same way (sorry, I missed that you're looking just not from type checking perspective). This is core principle of python typing, as opposed to strongly typed languages (which makes it wonderful IMO): you can always say "I don't need types here - save my time and mental health". Type annotations are used to help some third-party tools, like mypy (type checker maintained by python core team) and IDEs. IDEs can suggest you something based on this information, and mypy checks whether your code can work if your types match the reality.

Generic version

T = TypeVar('T')

class Stack(Generic[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()

    def empty(self) -> bool:
        return not self.items

You can treat type variables like regular variables, but intended for "meta" usage and ignored (well, there are some runtime traces, but they exist primary for introspection purpose) on runtime. They are substituted once for every binding context (more about it - below), and can be defined only once per module scope.

The code above declares normal generic class with one type argument. Now you can say Stack[int] to refer to a stack of integers, which is great. Current definition allows either explicit typing or using implicit Any parametrization:

# Explicit type
int_stack: Stack[int] = Stack()
reveal_type(int_stack)  # N: revealed type is "__main__.Stack[builtins.int]
int_stack.push(1)  # ok
int_stack.push('foo')  # E: Argument 1 to "push" of "Stack" has incompatible type "str"; expected "int"  [arg-type]
reveal_type(int_stack.pop())  # N: revealed type is "builtins.int"
# No type results in mypy error, similar to `x = []`
any_stack = Stack()  # E: need type annotation for any_stack
# But if you ignore it, the type becomes `Stack[Any]`
reveal_type(any_stack)  # N: revealed type is "__main__.Stack[Any]
any_stack.push(1)  # ok
any_stack.push('foo')  # ok too
reveal_type(any_stack.pop())  # N: revealed type is "Any"

To make the intended usage easier, you can allow initialization from iterable (I'm not covering the fact that you should be using collections.deque instead of list and maybe instead of this Stack class, assuming it is just a toy collection):

from collections.abc import Iterable

class Stack(Generic[T]):
    def __init__(self, items: Iterable[T] | None) -> None:
        # Create an empty list with items of type T
        self.items: list[T] = list(items or [])
    ...

deduced_int_stack = Stack([1])
reveal_type(deduced_int_stack)  # N: revealed type is "__main__.Stack[builtins.int]"

To sum up, generic classes have some type variable bound to the class body. When you create an instance of such class, it can be parametrized with some type - it may be another type variable or some fixed type, like int or tuple[str, Callable[[], MyClass[bool]]]. Then all occurrences of T in its body (except for nested classes, which are perhaps out of "quick glance" explanation context) are replaced with this type (or Any, if it is not given and cannot be deduced). This type can be deduced iff at least one of __init__ or __new__ arguments has type referring to T (just T or, say, list[T]), and otherwise you have to specify it. Note that if you have T used in __init__ of non-generic class, it is not very cool, although currently not disallowed.

Now, if you use T in some methods of generic class, it refers to that replaced value and results in typecheck errors, if passed types are not compatible with expected.

You can play with this example here.

Working outside of generic context

However, not all usages of type variables are related to generic classes. Fortunately, you cannot declare generic function with possibility to declare generic arg on calling side (like function<T> fun(x: number): T and fun<string>(0)), but there is enough more stuff. Let's begin with simpler examples - pure functions:

T = TypeVar('T')

def func1() -> T:
    return 1
def func2(x: T) -> int:
    return 1
def func3(x: T) -> T:
    return x
def func4(x: T, y: T) -> int:
    return 1

First function is declared to return some value of unbound type T. It obviously makes no sense, and recent mypy versions even learned to mark it as error. Your function return depends only on arguments and external state - and type variable must be present there, right? You cannot also declare global variable of type T in module scope, because T is still unbound - and thus neither func1 args nor module-scoped variables can depend on T.

Second function is more interesting. It does not cause mypy error, although still makes not very much sense: we can bind some type to T, but what is the difference between this and func2_1(x: Any) -> int: ...? We can speculate that now T can be used as annotation in function body, which can help in some corner case with type variable having upper bound parameterizing an invariant class used covariantly (imagine needing a list[Parent] with .count() method and __getitem__, but still allowing list[Child] to be passed in and ignoring both Sequence existence and custom protocol option). Similar example is even explicitly referenced in PEP as valid.

The third and fourth functions are typical examples of type variables in functions. The third declares function returning the same type as it's argument.

The fourth function takes two arguments of the same type (arbitrary one). It is more useful if you have T = TypeVar('T', bound=Something) or T = TypeVar('T', str, bytes): you can concatenate two arguments of type T, but cannot - of type str | bytes, like in the below example:

T = TypeVar('T', str, bytes)

def total_length(x: T, y: T) -> int:
    return len(x + y)

The most important fact about all examples above in this section: T doesnot have to be the same for different functions. You can call func3(1), then func3(['bar']) and then func4('foo', 'bar'). T is int, list[str] and str in these calls - no need to match.

With this in mind your second solution is clear:

T = TypeVar('T')

class Stack:
    def __init__(self) -> None:
        # Create an empty list with items of type T
        self.items: list[T] = []  # E: Type variable "__main__.T" is unbound  [valid-type]

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

    def pop(self) -> T:  # E: A function returning TypeVar should receive at least one argument containing the same TypeVar  [type-var]
        return self.items.pop()

Here is mypy issue, discussing similar case.

__init__ says that we set attribute x to value of type T, but this T is lost later (T is scoped only within __init__) - so mypy rejects the assignment.

push is ill-formed and T has no meaning here, but it does not result in invalid typing situation, so is not rejected (type of argument is erased to Any, so you still can call push with some argument).

pop is invalid, because typechecker needs to know what my_stack.pop() will return. It could say "I give up - just have your Any", and will be perfectly valid (PEP does not enforce this). but mypy is more smart and denies invalid-by-design usage.

Edge case: you can return SomeGeneric[T] with unbound T, for example, in factory functions:

def make_list() -> list[T]: ...

mylist: list[str] = make_list()

because otherwise type argument couldn't have been specified on calling site

For better understanding of type variables and generics in python, I suggest you to read PEP483 and PEP484 - usually PEPs are more like a boring standard, but these are really good as a starting point.

There are many edge cases omitted there, which still cause hot discussions in mypy team (and probably other typecheckers too) - say, type variables in staticmethods of generic classes, or binding in classmethods used as constructors - mind that they can be used on instances too. However, basically you can:

  • have a TypeVar bound to class (Generic or Protocol, or some Generic subclass - if you subclass Iterable[T], your class is already generic in T) - then all methods use the same T and can contain it in one or both sides
  • or have a method-scoped/function-scoped type variable - then it's useful if repeated in the signature more than once (not necessary "clean" - it may be parametrizing another generic)
  • or use type variables in generic aliases (like LongTuple = tuple[T, T, T, T] - then you can do x: LongTuple[int] = (1, 2, 3, 4)
  • or do something more exotic with type variables, which is probably out of scope
🌐
mypy
mypy.readthedocs.io › en › latest › generics.html
Generics - mypy 1.20.0+dev.acfef9c84da69805f740e67b108910888d66f7ab documentation
Here is the same example using the old syntax (required for Python 3.11 and earlier, but also supported on newer Python versions): from typing import TypeVar, Generic T = TypeVar('T') # Define type variable "T" class Stack(Generic[T]): def __init__(self) -> None: # Create an empty list with items of type T self.items: list[T] = [] def push(self, item: T) -> None: self.items.append(item) def pop(self) -> T: return self.items.pop() def empty(self) -> bool: return not self.items
🌐
Python.org
discuss.python.org › documentation
Plans to update Python's generics docs? - Documentation - Discussions on Python.org
2 days ago - The typing documentation for generics is somewhat inconsistent in the use of new syntax: Generics — typing documentation (https://typing.python.org/en/latest/reference/generics.html) doesn’t include the new syntax class Foo[T] at all Generics — typing documentation (https://typing.python.org/en/latest/spec/generics.html) does, and mentions PEP695 Are there any plans on updating reference/generics.html?
🌐
Python
peps.python.org › pep-0646
PEP 646 – Variadic Generics - Python Enhancement Proposals
February 1, 2025 - This PEP is a historical document. The up-to-date, canonical documentation can now be found at TypeVarTuple and typing.TypeVarTuple. ... See PEP 1 for how to propose changes. PEP 484 introduced TypeVar, enabling creation of generics parameterised with a single type.
🌐
Real Python
realpython.com › python312-typing
Python 3.12 Preview: Static Typing Improvements – Real Python
October 21, 2023 - In this tutorial, you'll preview the new static typing features in Python 3.12. You'll learn about the new syntax for type variables, making generics simpler to define. You'll also see how @override lets you model inheritance and how you use typed dictionaries to annotate variable keyword arguments.
🌐
GitHub
github.com › python › typing › blob › main › docs › spec › generics.rst
typing/docs/spec/generics.rst at main · python/typing
The introduction of explicit syntax for generic classes in Python 3.12 eliminates the need for variance to be specified for type parameters. Instead, type checkers will infer the variance of type parameters based on their usage within a class.
Author   python
🌐
ArjanCodes
arjancodes.com › blog › python-generics-syntax
Python 3.12 Generics: Cleaner, Easier Type-Hinting | ArjanCodes
June 6, 2024 - Learn how Python 3.12 generics make type-hinting easier and clearer. Use the new syntax to enhance your code's structure and readability.