Skip to content

Commit 21d46ed

Browse files
Chelsea DurazoJukkaL
Chelsea Durazo
authored andcommitted
documentation for TypeIs (#17821)
Fixes #17156. As requested in the issue, added documentation for the desired behaviour of TypeIs.
1 parent c692943 commit 21d46ed

File tree

1 file changed

+166
-0
lines changed

1 file changed

+166
-0
lines changed

docs/source/type_narrowing.rst

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,3 +389,169 @@ or rewrite the function to be slightly more verbose:
389389
elif b is not None:
390390
return b
391391
return C()
392+
393+
394+
.. _typeis:
395+
396+
TypeIs
397+
------
398+
399+
Mypy supports TypeIs (:pep:`742`).
400+
401+
A `TypeIs narrowing function <https://typing.readthedocs.io/en/latest/spec/narrowing.html#typeis>`_
402+
allows you to define custom type checks that can narrow the type of a variable
403+
in `both the if and else <https://docs.python.org/3.13/library/typing.html#typing.TypeIs>_`
404+
branches of a conditional, similar to how the built-in isinstance() function works.
405+
406+
TypeIs is new in Python 3.13 — for use in older Python versions, use the backport
407+
from `typing_extensions <https://typing-extensions.readthedocs.io/en/latest/>_`
408+
409+
Consider the following example using TypeIs:
410+
411+
.. code-block:: python
412+
413+
from typing import TypeIs
414+
415+
def is_str(x: object) -> TypeIs[str]:
416+
return isinstance(x, str)
417+
418+
def process(x: int | str) -> None:
419+
if is_str(x):
420+
reveal_type(x) # Revealed type is 'str'
421+
print(x.upper()) # Valid: x is str
422+
else:
423+
reveal_type(x) # Revealed type is 'int'
424+
print(x + 1) # Valid: x is int
425+
426+
In this example, the function is_str is a type narrowing function
427+
that returns TypeIs[str]. When used in an if statement, x is narrowed
428+
to str in the if branch and to int in the else branch.
429+
430+
Key points:
431+
432+
433+
- The function must accept at least one positional argument.
434+
435+
- The return type is annotated as ``TypeIs[T]``, where ``T`` is the type you
436+
want to narrow to.
437+
438+
- The function must return a ``bool`` value.
439+
440+
- In the ``if`` branch (when the function returns ``True``), the type of the
441+
argument is narrowed to the intersection of its original type and ``T``.
442+
443+
- In the ``else`` branch (when the function returns ``False``), the type of
444+
the argument is narrowed to the intersection of its original type and the
445+
complement of ``T``.
446+
447+
448+
TypeIs vs TypeGuard
449+
~~~~~~~~~~~~~~~~~~~
450+
451+
While both TypeIs and TypeGuard allow you to define custom type narrowing
452+
functions, they differ in important ways:
453+
454+
- **Type narrowing behavior**: TypeIs narrows the type in both the if and else branches,
455+
whereas TypeGuard narrows only in the if branch.
456+
457+
- **Compatibility requirement**: TypeIs requires that the narrowed type T be
458+
compatible with the input type of the function. TypeGuard does not have this restriction.
459+
460+
- **Type inference**: With TypeIs, the type checker may infer a more precise type by
461+
combining existing type information with T.
462+
463+
Here's an example demonstrating the behavior with TypeGuard:
464+
465+
.. code-block:: python
466+
467+
from typing import TypeGuard, reveal_type
468+
469+
def is_str(x: object) -> TypeGuard[str]:
470+
return isinstance(x, str)
471+
472+
def process(x: int | str) -> None:
473+
if is_str(x):
474+
reveal_type(x) # Revealed type is "builtins.str"
475+
print(x.upper()) # ok: x is str
476+
else:
477+
reveal_type(x) # Revealed type is "Union[builtins.int, builtins.str]"
478+
print(x + 1) # ERROR: Unsupported operand types for + ("str" and "int") [operator]
479+
480+
Generic TypeIs
481+
~~~~~~~~~~~~~~
482+
483+
``TypeIs`` functions can also work with generic types:
484+
485+
.. code-block:: python
486+
487+
from typing import TypeVar, TypeIs
488+
489+
T = TypeVar('T')
490+
491+
def is_two_element_tuple(val: tuple[T, ...]) -> TypeIs[tuple[T, T]]:
492+
return len(val) == 2
493+
494+
def process(names: tuple[str, ...]) -> None:
495+
if is_two_element_tuple(names):
496+
reveal_type(names) # Revealed type is 'tuple[str, str]'
497+
else:
498+
reveal_type(names) # Revealed type is 'tuple[str, ...]'
499+
500+
501+
TypeIs with Additional Parameters
502+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
503+
TypeIs functions can accept additional parameters beyond the first.
504+
The type narrowing applies only to the first argument.
505+
506+
.. code-block:: python
507+
508+
from typing import Any, TypeVar, reveal_type, TypeIs
509+
510+
T = TypeVar('T')
511+
512+
def is_instance_of(val: Any, typ: type[T]) -> TypeIs[T]:
513+
return isinstance(val, typ)
514+
515+
def process(x: Any) -> None:
516+
if is_instance_of(x, int):
517+
reveal_type(x) # Revealed type is 'int'
518+
print(x + 1) # ok
519+
else:
520+
reveal_type(x) # Revealed type is 'Any'
521+
522+
TypeIs in Methods
523+
~~~~~~~~~~~~~~~~~
524+
525+
A method can also serve as a ``TypeIs`` function. Note that in instance or
526+
class methods, the type narrowing applies to the second parameter
527+
(after ``self`` or ``cls``).
528+
529+
.. code-block:: python
530+
531+
class Validator:
532+
def is_valid(self, instance: object) -> TypeIs[str]:
533+
return isinstance(instance, str)
534+
535+
def process(self, to_validate: object) -> None:
536+
if Validator().is_valid(to_validate):
537+
reveal_type(to_validate) # Revealed type is 'str'
538+
print(to_validate.upper()) # ok: to_validate is str
539+
540+
541+
Assignment Expressions with TypeIs
542+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
543+
544+
You can use the assignment expression operator ``:=`` with ``TypeIs`` to create a new variable and narrow its type simultaneously.
545+
546+
.. code-block:: python
547+
548+
from typing import TypeIs, reveal_type
549+
550+
def is_float(x: object) -> TypeIs[float]:
551+
return isinstance(x, float)
552+
553+
def main(a: object) -> None:
554+
if is_float(x := a):
555+
reveal_type(x) # Revealed type is 'float'
556+
# x is narrowed to float in this block
557+
print(x + 1.0)

0 commit comments

Comments
 (0)