Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 51 additions & 71 deletions docs/reference/generics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,7 @@ Here is a very simple generic class that represents a stack:

.. code-block:: python

from typing import TypeVar, Generic

T = TypeVar('T')

class Stack(Generic[T]):
class Stack[T]:
def __init__(self) -> None:
# Create an empty list with items of type T
self.items: list[T] = []
Expand All @@ -37,6 +33,23 @@ Here is a very simple generic class that represents a stack:
def empty(self) -> bool:
return not self.items

.. note::

The type parameter syntax (e.g., ``class Foo[T]:``) is available in Python 3.12 and newer.
For earlier Python versions, generic classes need to be defined using
``TypeVar`` and ``Generic``, as shown below.

For compatibility with older Python versions, the same class may be written as:

.. code-block:: python

from typing import TypeVar, Generic

T = TypeVar('T')

class Stack(Generic[T]):
...

The ``Stack`` class can be used to represent a stack of any type:
``Stack[int]``, ``Stack[tuple[int, str]]``, etc.

Expand All @@ -56,7 +69,7 @@ construction of the instance will be type checked correspondingly.

.. code-block:: python

class Box(Generic[T]):
class Box[T]:
def __init__(self, content: T) -> None:
self.content = content

Expand All @@ -70,17 +83,14 @@ Defining subclasses of generic classes
**************************************

User-defined generic classes and generic classes defined in :py:mod:`typing`
can be used as a base class for another class (generic or non-generic). For example:
can be used as base classes for other classes (generic or non-generic). For example:

.. code-block:: python

from typing import Generic, TypeVar, Mapping, Iterator

KT = TypeVar('KT')
VT = TypeVar('VT')
from typing import Mapping, Iterator

# This is a generic subclass of Mapping
class MyMap(Mapping[KT, VT]):
class MyMap[KT, VT](Mapping[KT, VT]):
def __getitem__(self, k: KT) -> VT: ...
def __iter__(self) -> Iterator[KT]: ...
def __len__(self) -> int: ...
Expand All @@ -96,12 +106,12 @@ can be used as a base class for another class (generic or non-generic). For exam
data: StrDict[int, int] # error: "StrDict" expects no type arguments, but 2 given
data2: StrDict # OK

# This is a user-defined generic class
class Receiver(Generic[T]):
def accept(self, value: T) -> None: ...
# This is a user-defined generic class
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still uses the old syntax. Please make sure all examples (other than the one mentioning the old syntax explicitly) use the new syntax.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review @JelleZijlstra, I’ve updated the remaining example to use PEP 695 syntax so that all examples are now consistent, with the old syntax kept only in the explicitly marked compatibility section.

class Receiver[T]:
def accept(self, value: T) -> None: ...

# This is a generic subclass of Receiver
class AdvancedReceiver(Receiver[T]): ...
# This is a generic subclass of Receiver
class AdvancedReceiver[T](Receiver[T]): ...

.. note::

Expand All @@ -111,33 +121,16 @@ can be used as a base class for another class (generic or non-generic). For exam
protocols like :py:class:`~typing.Iterable`, which use
:ref:`structural subtyping <protocol-types>`.

:py:class:`Generic <typing.Generic>` can be omitted from bases if there are
other base classes that include type variables, such as ``Mapping[KT, VT]``
in the above example. If you include ``Generic[...]`` in bases, then
it should list all type variables present in other bases (or more,
if needed). The order of type variables is defined by the following
rules:

* If ``Generic[...]`` is present, then the order of variables is
always determined by their order in ``Generic[...]``.
* If there are no ``Generic[...]`` in bases, then all type variables
are collected in the lexicographic order (i.e. by first appearance).

For example:

.. code-block:: python

from typing import Generic, TypeVar, Any

T = TypeVar('T')
S = TypeVar('S')
U = TypeVar('U')
from typing import Any
class One[T]: ...
class Another[T]: ...

class One(Generic[T]): ...
class Another(Generic[T]): ...

class First(One[T], Another[S]): ...
class Second(One[T], Another[S], Generic[S, U, T]): ...
class First[T, S](One[T], Another[S]): ...
class Second[S, U, T](One[T], Another[S]): ...

x: First[int, str] # Here T is bound to int, S is bound to str
y: Second[int, str, Any] # Here T is Any, S is int, and U is str
Expand All @@ -152,12 +145,10 @@ where the types of the arguments or return value have some relationship:

.. code-block:: python

from typing import TypeVar, Sequence

T = TypeVar('T')
from typing import Sequence

# A generic function!
def first(seq: Sequence[T]) -> T:
def first[T](seq: Sequence[T]) -> T:
return seq[0]

As with generic classes, the type variable can be replaced with any
Expand All @@ -181,14 +172,12 @@ functions do not share any typing relationship to each other:

.. code-block:: python

from typing import TypeVar, Sequence
from typing import Sequence

T = TypeVar('T')

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

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

Variables should not have a type variable in their type unless the type variable
Expand All @@ -205,16 +194,16 @@ the class definition.

.. code-block:: python

# T is the type variable bound by this class
class PairedBox(Generic[T]):
# T is the type parameter bound by this class
class PairedBox[T]:
def __init__(self, content: T) -> None:
self.content = content

# S is a type variable bound only in this method
def first(self, x: list[S]) -> S:
# S is a type parameter bound only in this method
def first[S](self, x: list[S]) -> S:
return x[0]

def pair_with_first(self, x: list[S]) -> tuple[S, T]:
def pair_with_first[S](self, x: list[S]) -> tuple[S, T]:
return (x[0], self.content)

box = PairedBox("asdf")
Expand All @@ -228,12 +217,8 @@ methods:

.. code-block:: python

from typing import TypeVar

T = TypeVar('T', bound='Shape')

class Shape:
def set_scale(self: T, scale: float) -> T:
def set_scale[T: Shape](self: T, scale: float) -> T:
self.scale = scale
return self

Expand All @@ -259,15 +244,13 @@ For class methods, you can also define generic ``cls``, using :py:class:`type`:

.. code-block:: python

from typing import Optional, TypeVar, Type

T = TypeVar('T', bound='Friend')
from typing import Optional

class Friend:
other: Optional["Friend"] = None

@classmethod
def make_pair(cls: type[T]) -> tuple[T, T]:
def make_pair[T: Friend](cls: type[T]) -> tuple[T, T]:
a, b = cls(), cls()
a.other = b
b.other = a
Expand Down Expand Up @@ -458,25 +441,22 @@ It's therefore often useful to be able to limit the types that a type
variable can take on, for instance, by restricting it to values that are
subtypes of a specific type.

Such a type is called the upper bound of the type variable, and is specified
with the ``bound=...`` keyword argument to :py:class:`~typing.TypeVar`.
Such a type is called the upper bound of the type parameter. In modern Python
(3.12+), this can be expressed directly in the type parameter list using
``[T: Bound]`` syntax.

.. code-block:: python

from typing import TypeVar, SupportsAbs
from typing import SupportsAbs

T = TypeVar('T', bound=SupportsAbs[float])
def largest_in_absolute_value[T: SupportsAbs[float]](*xs: T) -> T:
return max(xs, key=abs)

In the definition of a generic function that uses such a type variable
``T``, the type represented by ``T`` is assumed to be a subtype of
its upper bound, so the function can use methods of the upper bound on
values of type ``T``.

.. code-block:: python

def largest_in_absolute_value(*xs: T) -> T:
return max(xs, key=abs) # Okay, because T is a subtype of SupportsAbs[float].

In a call to such a function, the type ``T`` must be replaced by a
type that is a subtype of its upper bound. Continuing the example
above:
Expand Down