Skip to content

Commit b3ff2a6

Browse files
Adds better type narrowing docs (#11088)
Co-authored-by: Shantanu <[email protected]>
1 parent b165e42 commit b3ff2a6

8 files changed

+307
-202
lines changed

docs/source/casts.rst

-49
This file was deleted.

docs/source/common_issues.rst

+4-2
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ Complex type tests
370370
Mypy can usually infer the types correctly when using :py:func:`isinstance <isinstance>`,
371371
:py:func:`issubclass <issubclass>`,
372372
or ``type(obj) is some_class`` type tests,
373-
and even user-defined type guards,
373+
and even :ref:`user-defined type guards <type-guards>`,
374374
but for other kinds of checks you may need to add an
375375
explicit type cast:
376376

@@ -385,7 +385,7 @@ explicit type cast:
385385
386386
found = a[index] # Has `object` type, despite the fact that we know it is `str`
387387
return cast(str, found) # So, we need an explicit cast to make mypy happy
388-
388+
389389
Alternatively, you can use ``assert`` statement together with some
390390
of the supported type inference techniques:
391391

@@ -729,6 +729,8 @@ not necessary:
729729
def test(self, t: List[int]) -> Sequence[str]: # type: ignore[override]
730730
...
731731
732+
.. _unreachable:
733+
732734
Unreachable code
733735
----------------
734736

docs/source/dynamic_typing.rst

+3-3
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ operations:
8080
n = 1 # type: int
8181
n = o # Error!
8282
83-
You can use :py:func:`~typing.cast` (see chapter :ref:`casts`) or :py:func:`isinstance` to
84-
go from a general type such as :py:class:`object` to a more specific
85-
type (subtype) such as ``int``. :py:func:`~typing.cast` is not needed with
83+
You can use different :ref:`type narrowing <type-narrowing>`
84+
techniques to narrow :py:class:`object` to a more specific
85+
type (subtype) such as ``int``. Type narrowing is not needed with
8686
dynamically typed values (values with type ``Any``).

docs/source/generics.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,7 @@ non-function (e.g. ``my_decorator(1)``) will be rejected.
562562
Also note that the ``wrapper()`` function is not type-checked. Wrapper
563563
functions are typically small enough that this is not a big
564564
problem. This is also the reason for the :py:func:`~typing.cast` call in the
565-
``return`` statement in ``my_decorator()``. See :ref:`casts`.
565+
``return`` statement in ``my_decorator()``. See :ref:`casts <casts>`.
566566

567567
.. _decorator-factories:
568568

docs/source/index.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Mypy is a static type checker for Python 3 and Python 2.7.
3939
protocols
4040
dynamic_typing
4141
python2
42-
casts
42+
type_narrowing
4343
duck_type_compatibility
4444
stubs
4545
generics

docs/source/more_types.rst

-145
Original file line numberDiff line numberDiff line change
@@ -1151,148 +1151,3 @@ section of the docs has a full description with an example, but in short, you wi
11511151
need to give each TypedDict the same key where each value has a unique
11521152
unique :ref:`Literal type <literal_types>`. Then, check that key to distinguish
11531153
between your TypedDicts.
1154-
1155-
1156-
User-Defined Type Guards
1157-
************************
1158-
1159-
Mypy supports User-Defined Type Guards
1160-
(:pep:`647`).
1161-
1162-
A type guard is a way for programs to influence conditional
1163-
type narrowing employed by a type checker based on runtime checks.
1164-
1165-
Basically, a ``TypeGuard`` is a "smart" alias for a ``bool`` type.
1166-
Let's have a look at the regular ``bool`` example:
1167-
1168-
.. code-block:: python
1169-
1170-
from typing import List
1171-
1172-
def is_str_list(val: List[object]) -> bool:
1173-
"""Determines whether all objects in the list are strings"""
1174-
return all(isinstance(x, str) for x in val)
1175-
1176-
def func1(val: List[object]) -> None:
1177-
if is_str_list(val):
1178-
reveal_type(val) # Reveals List[object]
1179-
print(" ".join(val)) # Error: incompatible type
1180-
1181-
The same example with ``TypeGuard``:
1182-
1183-
.. code-block:: python
1184-
1185-
from typing import List
1186-
from typing import TypeGuard # use `typing_extensions` for Python 3.9 and below
1187-
1188-
def is_str_list(val: List[object]) -> TypeGuard[List[str]]:
1189-
"""Determines whether all objects in the list are strings"""
1190-
return all(isinstance(x, str) for x in val)
1191-
1192-
def func1(val: List[object]) -> None:
1193-
if is_str_list(val):
1194-
reveal_type(val) # List[str]
1195-
print(" ".join(val)) # ok
1196-
1197-
How does it work? ``TypeGuard`` narrows the first function argument (``val``)
1198-
to the type specified as the first type parameter (``List[str]``).
1199-
1200-
.. note::
1201-
1202-
Narrowing is
1203-
`not strict <https://www.python.org/dev/peps/pep-0647/#enforcing-strict-narrowing>`_.
1204-
For example, you can narrow ``str`` to ``int``:
1205-
1206-
.. code-block:: python
1207-
1208-
def f(value: str) -> TypeGuard[int]:
1209-
return True
1210-
1211-
Note: since strict narrowing is not enforced, it's easy
1212-
to break type safety.
1213-
1214-
However, there are many ways a determined or uninformed developer can
1215-
subvert type safety -- most commonly by using cast or Any.
1216-
If a Python developer takes the time to learn about and implement
1217-
user-defined type guards within their code,
1218-
it is safe to assume that they are interested in type safety
1219-
and will not write their type guard functions in a way
1220-
that will undermine type safety or produce nonsensical results.
1221-
1222-
Generic TypeGuards
1223-
------------------
1224-
1225-
``TypeGuard`` can also work with generic types:
1226-
1227-
.. code-block:: python
1228-
1229-
from typing import Tuple, TypeVar
1230-
from typing import TypeGuard # use `typing_extensions` for `python<3.10`
1231-
1232-
_T = TypeVar("_T")
1233-
1234-
def is_two_element_tuple(val: Tuple[_T, ...]) -> TypeGuard[Tuple[_T, _T]]:
1235-
return len(val) == 2
1236-
1237-
def func(names: Tuple[str, ...]):
1238-
if is_two_element_tuple(names):
1239-
reveal_type(names) # Tuple[str, str]
1240-
else:
1241-
reveal_type(names) # Tuple[str, ...]
1242-
1243-
Typeguards with parameters
1244-
--------------------------
1245-
1246-
Type guard functions can accept extra arguments:
1247-
1248-
.. code-block:: python
1249-
1250-
from typing import Type, Set, TypeVar
1251-
from typing import TypeGuard # use `typing_extensions` for `python<3.10`
1252-
1253-
_T = TypeVar("_T")
1254-
1255-
def is_set_of(val: Set[Any], type: Type[_T]) -> TypeGuard[Set[_T]]:
1256-
return all(isinstance(x, type) for x in val)
1257-
1258-
items: Set[Any]
1259-
if is_set_of(items, str):
1260-
reveal_type(items) # Set[str]
1261-
1262-
TypeGuards as methods
1263-
---------------------
1264-
1265-
A method can also serve as the ``TypeGuard``:
1266-
1267-
.. code-block:: python
1268-
1269-
class StrValidator:
1270-
def is_valid(self, instance: object) -> TypeGuard[str]:
1271-
return isinstance(instance, str)
1272-
1273-
def func(to_validate: object) -> None:
1274-
if StrValidator().is_valid(to_validate):
1275-
reveal_type(to_validate) # Revealed type is "builtins.str"
1276-
1277-
.. note::
1278-
1279-
Note, that ``TypeGuard``
1280-
`does not narrow <https://www.python.org/dev/peps/pep-0647/#narrowing-of-implicit-self-and-cls-parameters>`_
1281-
types of ``self`` or ``cls`` implicit arguments.
1282-
1283-
If narrowing of ``self`` or ``cls`` is required,
1284-
the value can be passed as an explicit argument to a type guard function:
1285-
1286-
.. code-block:: python
1287-
1288-
class Parent:
1289-
def method(self) -> None:
1290-
reveal_type(self) # Revealed type is "Parent"
1291-
if is_child(self):
1292-
reveal_type(self) # Revealed type is "Child"
1293-
1294-
class Child(Parent):
1295-
...
1296-
1297-
def is_child(instance: Parent) -> TypeGuard[Child]:
1298-
return isinstance(instance, Child)

docs/source/runtime_troubles.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ required to be valid Python syntax. For more details, see :pep:`563`.
6262
of forward references or generics in:
6363

6464
* :ref:`type aliases <type-aliases>`;
65-
* :ref:`casts <casts>`;
65+
* :ref:`type narrowing <type-narrowing>`;
6666
* type definitions (see :py:class:`~typing.TypeVar`, :py:func:`~typing.NewType`, :py:class:`~typing.NamedTuple`);
6767
* base classes.
6868

0 commit comments

Comments
 (0)