Skip to content

Commit 7f3d7f8

Browse files
authored
[PEP 695] Further documentation updates (#17826)
Finish work started in #17816. Document `type` statement when discussing type aliases. Update some examples to have both old-style and new-style variants. In less common scenarios, examples only use a single syntax variant to reduce verbosity. Also update some examples to generally use more modern features. Closes #17810.
1 parent 58825f7 commit 7f3d7f8

10 files changed

+135
-87
lines changed

docs/source/additional_features.rst

+7-11
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,18 @@ define dataclasses. For example:
4646
UnorderedPoint(1, 2) < UnorderedPoint(3, 4) # Error: Unsupported operand types
4747
4848
Dataclasses can be generic and can be used in any other way a normal
49-
class can be used:
49+
class can be used (Python 3.12 syntax):
5050

5151
.. code-block:: python
5252
5353
from dataclasses import dataclass
54-
from typing import Generic, TypeVar
55-
56-
T = TypeVar('T')
5754
5855
@dataclass
59-
class BoxedData(Generic[T]):
56+
class BoxedData[T]:
6057
data: T
6158
label: str
6259
63-
def unbox(bd: BoxedData[T]) -> T:
60+
def unbox[T](bd: BoxedData[T]) -> T:
6461
...
6562
6663
val = unbox(BoxedData(42, "<important>")) # OK, inferred type is int
@@ -98,17 +95,16 @@ does **not** work:
9895
9996
10097
To have Mypy recognize a wrapper of :py:func:`dataclasses.dataclass <dataclasses.dataclass>`
101-
as a dataclass decorator, consider using the :py:func:`~typing.dataclass_transform` decorator:
98+
as a dataclass decorator, consider using the :py:func:`~typing.dataclass_transform`
99+
decorator (example uses Python 3.12 syntax):
102100

103101
.. code-block:: python
104102
105103
from dataclasses import dataclass, Field
106-
from typing import TypeVar, dataclass_transform
107-
108-
T = TypeVar('T')
104+
from typing import dataclass_transform
109105
110106
@dataclass_transform(field_specifiers=(Field,))
111-
def my_dataclass(cls: type[T]) -> type[T]:
107+
def my_dataclass[T](cls: type[T]) -> type[T]:
112108
...
113109
return dataclass(cls)
114110

docs/source/cheat_sheet_py3.rst

+14-1
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,20 @@ Decorators
349349
**********
350350

351351
Decorator functions can be expressed via generics. See
352-
:ref:`declaring-decorators` for more details.
352+
:ref:`declaring-decorators` for more details. Example using Python 3.12
353+
syntax:
354+
355+
.. code-block:: python
356+
357+
from typing import Any, Callable
358+
359+
def bare_decorator[F: Callable[..., Any]](func: F) -> F:
360+
...
361+
362+
def decorator_args[F: Callable[..., Any]](url: str) -> Callable[[F], F]:
363+
...
364+
365+
The same example using pre-3.12 syntax:
353366

354367
.. code-block:: python
355368

docs/source/error_code_list.rst

+7-13
Original file line numberDiff line numberDiff line change
@@ -434,15 +434,11 @@ Check type variable values [type-var]
434434
Mypy checks that value of a type variable is compatible with a value
435435
restriction or the upper bound type.
436436

437-
Example:
437+
Example (Python 3.12 syntax):
438438

439439
.. code-block:: python
440440
441-
from typing import TypeVar
442-
443-
T1 = TypeVar('T1', int, float)
444-
445-
def add(x: T1, y: T1) -> T1:
441+
def add[T1: (int, float)](x: T1, y: T1) -> T1:
446442
return x + y
447443
448444
add(4, 5.5) # OK
@@ -783,27 +779,25 @@ Example:
783779
Safe handling of abstract type object types [type-abstract]
784780
-----------------------------------------------------------
785781

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

793789
.. code-block:: python
794790
795791
from abc import ABCMeta, abstractmethod
796-
from typing import List, Type, TypeVar
797792
798793
class Config(metaclass=ABCMeta):
799794
@abstractmethod
800795
def get_value(self, attr: str) -> str: ...
801796
802-
T = TypeVar("T")
803-
def make_many(typ: Type[T], n: int) -> List[T]:
797+
def make_many[T](typ: type[T], n: int) -> list[T]:
804798
return [typ() for _ in range(n)] # This will raise if typ is abstract
805799
806-
# Error: Only concrete class can be given where "Type[Config]" is expected [type-abstract]
800+
# Error: Only concrete class can be given where "type[Config]" is expected [type-abstract]
807801
make_many(Config, 5)
808802
809803
.. _code-safe-super:

docs/source/kinds_of_types.rst

+37-10
Original file line numberDiff line numberDiff line change
@@ -434,19 +434,20 @@ the runtime with some limitations (see :ref:`runtime_troubles`).
434434
Type aliases
435435
************
436436

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

439440
.. code-block:: python
440441
441-
def f() -> Union[list[dict[tuple[int, str], set[int]]], tuple[str, list[str]]]:
442+
def f() -> list[dict[tuple[int, str], set[int]]] | tuple[str, list[str]]:
442443
...
443444
444445
When cases like this arise, you can define a type alias by simply
445-
assigning the type to a variable:
446+
assigning the type to a variable (this is an *implicit type alias*):
446447

447448
.. code-block:: python
448449
449-
AliasType = Union[list[dict[tuple[int, str], set[int]]], tuple[str, list[str]]]
450+
AliasType = list[dict[tuple[int, str], set[int]]] | tuple[str, list[str]]
450451
451452
# Now we can use AliasType in place of the full name:
452453
@@ -459,8 +460,18 @@ assigning the type to a variable:
459460
another type -- it's equivalent to the target type except for
460461
:ref:`generic aliases <generic-type-aliases>`.
461462

462-
Since Mypy 0.930 you can also use *explicit type aliases*, which were
463-
introduced in :pep:`613`.
463+
Python 3.12 introduced the ``type`` statement for defining *explicit type aliases*.
464+
Explicit type aliases are unambiguous and can also improve readability by
465+
making the intent clear:
466+
467+
.. code-block:: python
468+
469+
type AliasType = list[dict[tuple[int, str], set[int]]] | tuple[str, list[str]]
470+
471+
# Now we can use AliasType in place of the full name:
472+
473+
def f() -> AliasType:
474+
...
464475
465476
There can be confusion about exactly when an assignment defines an implicit type alias --
466477
for example, when the alias contains forward references, invalid types, or violates some other
@@ -469,8 +480,17 @@ distinction between an unannotated variable and a type alias is implicit,
469480
ambiguous or incorrect type alias declarations default to defining
470481
a normal variable instead of a type alias.
471482

472-
Explicit type aliases are unambiguous and can also improve readability by
473-
making the intent clear:
483+
Aliases defined using the ``type`` statement have these properties, which
484+
distinguish them from implicit type aliases:
485+
486+
* The definition may contain forward references without having to use string
487+
literal escaping, since it is evaluated lazily.
488+
* The alias can be used in type annotations, type arguments, and casts, but
489+
it can't be used in contexts which require a class object. For example, it's
490+
not valid as a base class and it can't be used to construct instances.
491+
492+
There is also use an older syntax for defining explicit type aliases, which was
493+
introduced in Python 3.10 (:pep:`613`):
474494

475495
.. code-block:: python
476496
@@ -604,14 +624,21 @@ doesn't see that the ``buyer`` variable has type ``ProUser``:
604624
buyer.pay() # Rejected, not a method on User
605625
606626
However, using the ``type[C]`` syntax and a type variable with an upper bound (see
607-
:ref:`type-variable-upper-bound`) we can do better:
627+
:ref:`type-variable-upper-bound`) we can do better (Python 3.12 syntax):
628+
629+
.. code-block:: python
630+
631+
def new_user[U: User](user_class: type[U]) -> U:
632+
# Same implementation as before
633+
634+
Here is the example using the legacy syntax (Python 3.11 and earlier):
608635

609636
.. code-block:: python
610637
611638
U = TypeVar('U', bound=User)
612639
613640
def new_user(user_class: type[U]) -> U:
614-
# Same implementation as before
641+
# Same implementation as before
615642
616643
Now mypy will infer the correct type of the result when we call
617644
``new_user()`` with a specific subclass of ``User``:

docs/source/literal_types.rst

+3-7
Original file line numberDiff line numberDiff line change
@@ -264,19 +264,15 @@ use the same technique with regular objects, tuples, or namedtuples.
264264
Similarly, tags do not need to be specifically str Literals: they can be any type
265265
you can normally narrow within ``if`` statements and the like. For example, you
266266
could have your tags be int or Enum Literals or even regular classes you narrow
267-
using ``isinstance()``:
267+
using ``isinstance()`` (Python 3.12 syntax):
268268

269269
.. code-block:: python
270270
271-
from typing import Generic, TypeVar, Union
272-
273-
T = TypeVar('T')
274-
275-
class Wrapper(Generic[T]):
271+
class Wrapper[T]:
276272
def __init__(self, inner: T) -> None:
277273
self.inner = inner
278274
279-
def process(w: Union[Wrapper[int], Wrapper[str]]) -> None:
275+
def process(w: Wrapper[int] | Wrapper[str]) -> None:
280276
# Doing `if isinstance(w, Wrapper[int])` does not work: isinstance requires
281277
# that the second argument always be an *erased* type, with no generics.
282278
# This is because generics are a typing-only concept and do not exist at

docs/source/metaclasses.rst

+5-3
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,12 @@ Mypy supports the lookup of attributes in the metaclass:
3434

3535
.. code-block:: python
3636
37-
from typing import Type, TypeVar, ClassVar
38-
T = TypeVar('T')
37+
from typing import ClassVar, Self
3938
4039
class M(type):
4140
count: ClassVar[int] = 0
4241
43-
def make(cls: Type[T]) -> T:
42+
def make(cls) -> Self:
4443
M.count += 1
4544
return cls()
4645
@@ -56,6 +55,9 @@ Mypy supports the lookup of attributes in the metaclass:
5655
b: B = B.make() # metaclasses are inherited
5756
print(B.count + " objects were created") # Error: Unsupported operand types for + ("int" and "str")
5857
58+
.. note::
59+
In Python 3.10 and earlier, ``Self`` is available in ``typing_extensions``.
60+
5961
.. _limitations:
6062

6163
Gotchas and limitations of metaclass support

docs/source/more_types.rst

+44-23
Original file line numberDiff line numberDiff line change
@@ -256,11 +256,34 @@ method receives an integer we return a single item. If it receives a
256256
``slice``, we return a :py:class:`~typing.Sequence` of items.
257257

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

261261
.. code-block:: python
262262
263-
from typing import Sequence, TypeVar, Union, overload
263+
from collections.abc import Sequence
264+
from typing import overload
265+
266+
class MyList[T](Sequence[T]):
267+
@overload
268+
def __getitem__(self, index: int) -> T: ...
269+
270+
@overload
271+
def __getitem__(self, index: slice) -> Sequence[T]: ...
272+
273+
def __getitem__(self, index: int | slice) -> T | Sequence[T]:
274+
if isinstance(index, int):
275+
# Return a T here
276+
elif isinstance(index, slice):
277+
# Return a sequence of Ts here
278+
else:
279+
raise TypeError(...)
280+
281+
Here is the same example using the legacy syntax (Python 3.11 and earlier):
282+
283+
.. code-block:: python
284+
285+
from collections.abc import Sequence
286+
from typing import TypeVar, Union, overload
264287
265288
T = TypeVar('T')
266289
@@ -697,14 +720,13 @@ Restricted methods in generic classes
697720
-------------------------------------
698721

699722
In generic classes some methods may be allowed to be called only
700-
for certain values of type arguments:
723+
for certain values of type arguments (Python 3.12 syntax):
701724

702725
.. code-block:: python
703726
704-
T = TypeVar('T')
705-
706-
class Tag(Generic[T]):
727+
class Tag[T]:
707728
item: T
729+
708730
def uppercase_item(self: Tag[str]) -> str:
709731
return self.item.upper()
710732
@@ -714,18 +736,18 @@ for certain values of type arguments:
714736
ts.uppercase_item() # This is OK
715737
716738
This pattern also allows matching on nested types in situations where the type
717-
argument is itself generic:
739+
argument is itself generic (Python 3.12 syntax):
718740

719741
.. code-block:: python
720742
721-
T = TypeVar('T', covariant=True)
722-
S = TypeVar('S')
743+
from collections.abc import Sequence
723744
724-
class Storage(Generic[T]):
745+
class Storage[T]:
725746
def __init__(self, content: T) -> None:
726-
self.content = content
727-
def first_chunk(self: Storage[Sequence[S]]) -> S:
728-
return self.content[0]
747+
self._content = content
748+
749+
def first_chunk[S](self: Storage[Sequence[S]]) -> S:
750+
return self._content[0]
729751
730752
page: Storage[list[str]]
731753
page.first_chunk() # OK, type is "str"
@@ -734,13 +756,13 @@ argument is itself generic:
734756
# "first_chunk" with type "Callable[[Storage[Sequence[S]]], S]"
735757
736758
Finally, one can use overloads on self-type to express precise types of
737-
some tricky methods:
759+
some tricky methods (Python 3.12 syntax):
738760

739761
.. code-block:: python
740762
741-
T = TypeVar('T')
763+
from typing import overload, Callable
742764
743-
class Tag(Generic[T]):
765+
class Tag[T]:
744766
@overload
745767
def export(self: Tag[str]) -> str: ...
746768
@overload
@@ -799,23 +821,22 @@ Precise typing of alternative constructors
799821
------------------------------------------
800822

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

804827
.. code-block:: python
805828
806-
T = TypeVar('T')
807-
808-
class Base(Generic[T]):
809-
Q = TypeVar('Q', bound='Base[T]')
829+
from typing import Self
810830
831+
class Base[T]:
811832
def __init__(self, item: T) -> None:
812833
self.item = item
813834
814835
@classmethod
815-
def make_pair(cls: Type[Q], item: T) -> tuple[Q, Q]:
836+
def make_pair(cls, item: T) -> tuple[Self, Self]:
816837
return cls(item), cls(item)
817838
818-
class Sub(Base[T]):
839+
class Sub[T](Base[T]):
819840
...
820841
821842
pair = Sub.make_pair('yes') # Type is "tuple[Sub[str], Sub[str]]"

0 commit comments

Comments
 (0)