Skip to content

[PEP 695] Further documentation updates #17826

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
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
18 changes: 7 additions & 11 deletions docs/source/additional_features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,18 @@ define dataclasses. For example:
UnorderedPoint(1, 2) < UnorderedPoint(3, 4) # Error: Unsupported operand types

Dataclasses can be generic and can be used in any other way a normal
class can be used:
class can be used (Python 3.12 syntax):

.. code-block:: python

from dataclasses import dataclass
from typing import Generic, TypeVar

T = TypeVar('T')

@dataclass
class BoxedData(Generic[T]):
class BoxedData[T]:
data: T
label: str

def unbox(bd: BoxedData[T]) -> T:
def unbox[T](bd: BoxedData[T]) -> T:
...

val = unbox(BoxedData(42, "<important>")) # OK, inferred type is int
Expand Down Expand Up @@ -98,17 +95,16 @@ does **not** work:


To have Mypy recognize a wrapper of :py:func:`dataclasses.dataclass <dataclasses.dataclass>`
as a dataclass decorator, consider using the :py:func:`~typing.dataclass_transform` decorator:
as a dataclass decorator, consider using the :py:func:`~typing.dataclass_transform`
decorator (example uses Python 3.12 syntax):

.. code-block:: python

from dataclasses import dataclass, Field
from typing import TypeVar, dataclass_transform

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

@dataclass_transform(field_specifiers=(Field,))
def my_dataclass(cls: type[T]) -> type[T]:
def my_dataclass[T](cls: type[T]) -> type[T]:
...
return dataclass(cls)

Expand Down
15 changes: 14 additions & 1 deletion docs/source/cheat_sheet_py3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,20 @@ Decorators
**********

Decorator functions can be expressed via generics. See
:ref:`declaring-decorators` for more details.
:ref:`declaring-decorators` for more details. Example using Python 3.12
syntax:

.. code-block:: python

from typing import Any, Callable

def bare_decorator[F: Callable[..., Any]](func: F) -> F:
...

def decorator_args[F: Callable[..., Any]](url: str) -> Callable[[F], F]:
...

The same example using pre-3.12 syntax:

.. code-block:: python

Expand Down
20 changes: 7 additions & 13 deletions docs/source/error_code_list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -434,15 +434,11 @@ Check type variable values [type-var]
Mypy checks that value of a type variable is compatible with a value
restriction or the upper bound type.

Example:
Example (Python 3.12 syntax):

.. code-block:: python

from typing import TypeVar

T1 = TypeVar('T1', int, float)

def add(x: T1, y: T1) -> T1:
def add[T1: (int, float)](x: T1, y: T1) -> T1:
return x + y

add(4, 5.5) # OK
Expand Down Expand Up @@ -783,27 +779,25 @@ Example:
Safe handling of abstract type object types [type-abstract]
-----------------------------------------------------------

Mypy always allows instantiating (calling) type objects typed as ``Type[t]``,
Mypy always allows instantiating (calling) type objects typed as ``type[t]``,
even if it is not known that ``t`` is non-abstract, since it is a common
pattern to create functions that act as object factories (custom constructors).
Therefore, to prevent issues described in the above section, when an abstract
type object is passed where ``Type[t]`` is expected, mypy will give an error.
Example:
type object is passed where ``type[t]`` is expected, mypy will give an error.
Example (Python 3.12 syntax):

.. code-block:: python

from abc import ABCMeta, abstractmethod
from typing import List, Type, TypeVar

class Config(metaclass=ABCMeta):
@abstractmethod
def get_value(self, attr: str) -> str: ...

T = TypeVar("T")
def make_many(typ: Type[T], n: int) -> List[T]:
def make_many[T](typ: type[T], n: int) -> list[T]:
return [typ() for _ in range(n)] # This will raise if typ is abstract

# Error: Only concrete class can be given where "Type[Config]" is expected [type-abstract]
# Error: Only concrete class can be given where "type[Config]" is expected [type-abstract]
make_many(Config, 5)

.. _code-safe-super:
Expand Down
47 changes: 37 additions & 10 deletions docs/source/kinds_of_types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -434,19 +434,20 @@ the runtime with some limitations (see :ref:`runtime_troubles`).
Type aliases
************

In certain situations, type names may end up being long and painful to type:
In certain situations, type names may end up being long and painful to type,
especially if they are used frequently:

.. code-block:: python

def f() -> Union[list[dict[tuple[int, str], set[int]]], tuple[str, list[str]]]:
def f() -> list[dict[tuple[int, str], set[int]]] | tuple[str, list[str]]:
...

When cases like this arise, you can define a type alias by simply
assigning the type to a variable:
assigning the type to a variable (this is an *implicit type alias*):

.. code-block:: python

AliasType = Union[list[dict[tuple[int, str], set[int]]], tuple[str, list[str]]]
AliasType = list[dict[tuple[int, str], set[int]]] | tuple[str, list[str]]

# Now we can use AliasType in place of the full name:

Expand All @@ -459,8 +460,18 @@ assigning the type to a variable:
another type -- it's equivalent to the target type except for
:ref:`generic aliases <generic-type-aliases>`.

Since Mypy 0.930 you can also use *explicit type aliases*, which were
introduced in :pep:`613`.
Python 3.12 introduced the ``type`` statement for defining *explicit type aliases*.
Explicit type aliases are unambiguous and can also improve readability by
making the intent clear:

.. code-block:: python

type AliasType = list[dict[tuple[int, str], set[int]]] | tuple[str, list[str]]

# Now we can use AliasType in place of the full name:

def f() -> AliasType:
...

There can be confusion about exactly when an assignment defines an implicit type alias --
for example, when the alias contains forward references, invalid types, or violates some other
Expand All @@ -469,8 +480,17 @@ distinction between an unannotated variable and a type alias is implicit,
ambiguous or incorrect type alias declarations default to defining
a normal variable instead of a type alias.

Explicit type aliases are unambiguous and can also improve readability by
making the intent clear:
Aliases defined using the ``type`` statement have these properties, which
distinguish them from implicit type aliases:

* The definition may contain forward references without having to use string
literal escaping, since it is evaluated lazily.
* The alias can be used in type annotations, type arguments, and casts, but
it can't be used in contexts which require a class object. For example, it's
not valid as a base class and it can't be used to construct instances.

There is also use an older syntax for defining explicit type aliases, which was
introduced in Python 3.10 (:pep:`613`):

.. code-block:: python

Expand Down Expand Up @@ -604,14 +624,21 @@ doesn't see that the ``buyer`` variable has type ``ProUser``:
buyer.pay() # Rejected, not a method on User

However, using the ``type[C]`` syntax and a type variable with an upper bound (see
:ref:`type-variable-upper-bound`) we can do better:
:ref:`type-variable-upper-bound`) we can do better (Python 3.12 syntax):

.. code-block:: python

def new_user[U: User](user_class: type[U]) -> U:
# Same implementation as before

Here is the example using the legacy syntax (Python 3.11 and earlier):

.. code-block:: python

U = TypeVar('U', bound=User)

def new_user(user_class: type[U]) -> U:
# Same implementation as before
# Same implementation as before

Now mypy will infer the correct type of the result when we call
``new_user()`` with a specific subclass of ``User``:
Expand Down
10 changes: 3 additions & 7 deletions docs/source/literal_types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -264,19 +264,15 @@ use the same technique with regular objects, tuples, or namedtuples.
Similarly, tags do not need to be specifically str Literals: they can be any type
you can normally narrow within ``if`` statements and the like. For example, you
could have your tags be int or Enum Literals or even regular classes you narrow
using ``isinstance()``:
using ``isinstance()`` (Python 3.12 syntax):

.. code-block:: python

from typing import Generic, TypeVar, Union

T = TypeVar('T')

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

def process(w: Union[Wrapper[int], Wrapper[str]]) -> None:
def process(w: Wrapper[int] | Wrapper[str]) -> None:
# Doing `if isinstance(w, Wrapper[int])` does not work: isinstance requires
# that the second argument always be an *erased* type, with no generics.
# This is because generics are a typing-only concept and do not exist at
Expand Down
8 changes: 5 additions & 3 deletions docs/source/metaclasses.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,12 @@ Mypy supports the lookup of attributes in the metaclass:

.. code-block:: python

from typing import Type, TypeVar, ClassVar
T = TypeVar('T')
from typing import ClassVar, Self

class M(type):
count: ClassVar[int] = 0

def make(cls: Type[T]) -> T:
def make(cls) -> Self:
M.count += 1
return cls()

Expand All @@ -56,6 +55,9 @@ Mypy supports the lookup of attributes in the metaclass:
b: B = B.make() # metaclasses are inherited
print(B.count + " objects were created") # Error: Unsupported operand types for + ("int" and "str")

.. note::
In Python 3.10 and earlier, ``Self`` is available in ``typing_extensions``.

.. _limitations:

Gotchas and limitations of metaclass support
Expand Down
67 changes: 44 additions & 23 deletions docs/source/more_types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -256,11 +256,34 @@ method receives an integer we return a single item. If it receives a
``slice``, we return a :py:class:`~typing.Sequence` of items.

We can precisely encode this relationship between the argument and the
return type by using overloads like so:
return type by using overloads like so (Python 3.12 syntax):

.. code-block:: python

from typing import Sequence, TypeVar, Union, overload
from collections.abc import Sequence
from typing import overload

class MyList[T](Sequence[T]):
@overload
def __getitem__(self, index: int) -> T: ...

@overload
def __getitem__(self, index: slice) -> Sequence[T]: ...

def __getitem__(self, index: int | slice) -> T | Sequence[T]:
if isinstance(index, int):
# Return a T here
elif isinstance(index, slice):
# Return a sequence of Ts here
else:
raise TypeError(...)

Here is the same example using the legacy syntax (Python 3.11 and earlier):

.. code-block:: python

from collections.abc import Sequence
from typing import TypeVar, Union, overload

T = TypeVar('T')

Expand Down Expand Up @@ -697,14 +720,13 @@ Restricted methods in generic classes
-------------------------------------

In generic classes some methods may be allowed to be called only
for certain values of type arguments:
for certain values of type arguments (Python 3.12 syntax):

.. code-block:: python

T = TypeVar('T')

class Tag(Generic[T]):
class Tag[T]:
item: T

def uppercase_item(self: Tag[str]) -> str:
return self.item.upper()

Expand All @@ -714,18 +736,18 @@ for certain values of type arguments:
ts.uppercase_item() # This is OK

This pattern also allows matching on nested types in situations where the type
argument is itself generic:
argument is itself generic (Python 3.12 syntax):

.. code-block:: python

T = TypeVar('T', covariant=True)
S = TypeVar('S')
from collections.abc import Sequence

class Storage(Generic[T]):
class Storage[T]:
def __init__(self, content: T) -> None:
self.content = content
def first_chunk(self: Storage[Sequence[S]]) -> S:
return self.content[0]
self._content = content

def first_chunk[S](self: Storage[Sequence[S]]) -> S:
return self._content[0]

page: Storage[list[str]]
page.first_chunk() # OK, type is "str"
Expand All @@ -734,13 +756,13 @@ argument is itself generic:
# "first_chunk" with type "Callable[[Storage[Sequence[S]]], S]"

Finally, one can use overloads on self-type to express precise types of
some tricky methods:
some tricky methods (Python 3.12 syntax):

.. code-block:: python

T = TypeVar('T')
from typing import overload, Callable

class Tag(Generic[T]):
class Tag[T]:
@overload
def export(self: Tag[str]) -> str: ...
@overload
Expand Down Expand Up @@ -799,23 +821,22 @@ Precise typing of alternative constructors
------------------------------------------

Some classes may define alternative constructors. If these
classes are generic, self-type allows giving them precise signatures:
classes are generic, self-type allows giving them precise
signatures (Python 3.12 syntax):

.. code-block:: python

T = TypeVar('T')

class Base(Generic[T]):
Q = TypeVar('Q', bound='Base[T]')
from typing import Self

class Base[T]:
def __init__(self, item: T) -> None:
self.item = item

@classmethod
def make_pair(cls: Type[Q], item: T) -> tuple[Q, Q]:
def make_pair(cls, item: T) -> tuple[Self, Self]:
return cls(item), cls(item)

class Sub(Base[T]):
class Sub[T](Base[T]):
...

pair = Sub.make_pair('yes') # Type is "tuple[Sub[str], Sub[str]]"
Expand Down
Loading