-
-
Notifications
You must be signed in to change notification settings - Fork 3k
documentation for TypeIs #17821
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
documentation for TypeIs #17821
Changes from 2 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
8ee9c2e
documentation for TypeIs
0124a3e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 2151764
add links and apply suggested changes
d74650a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] d3f00d9
Merge branch 'python:master' into documentTypeIs
4db9d14
fix unexpected unindent in bullet list
cc0cd4c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 98a91ce
Merge branch 'python:master' into documentTypeIs
c3c9fa9
fix PEP number, duplicate paragraph and revealed type for int
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -395,3 +395,157 @@ or rewrite the function to be slightly more verbose: | |
elif b is not None: | ||
return b | ||
return C() | ||
|
||
|
||
.. _typeis: | ||
|
||
TypeIs | ||
------ | ||
|
||
Mypy should support TypeIs (:pep:`754`). | ||
|
||
A TypeIs function allows you to define custom type checks that | ||
chelseadz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
can narrow the type of a variable in both the if and else branches | ||
of a conditional, similar to how the built-in isinstance() function works. | ||
|
||
chelseadz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Consider the following example using TypeIs: | ||
|
||
.. code-block:: python | ||
|
||
from typing import TypeIs | ||
|
||
def is_str(x: object) -> TypeIs[str]: | ||
return isinstance(x, str) | ||
|
||
def process(x: int | str) -> None: | ||
if is_str(x): | ||
reveal_type(x) # Revealed type is 'str' | ||
print(x.upper()) # Valid: x is str | ||
else: | ||
reveal_type(x) # Revealed type is 'int' | ||
print(x + 1) # Valid: x is int | ||
|
||
In this example, the function is_str is a type narrowing function | ||
that returns TypeIs[str]. When used in an if statement, x is narrowed | ||
to str in the if branch and to int in the else branch. | ||
|
||
Key points: | ||
|
||
- The function must accept at least one positional argument. | ||
- The return type is annotated as ``TypeIs[T]``, where ``T`` is the type you | ||
want to narrow to. | ||
- The function must return a ``bool`` value. | ||
- In the ``if`` branch (when the function returns ``True``), the type of the | ||
argument is narrowed to the intersection of its original type and ``T``. | ||
- In the ``else`` branch (when the function returns ``False``), the type of | ||
the argument is narrowed to the intersection of its original type and the | ||
complement of ``T``. | ||
|
||
TypeIs vs TypeGuard | ||
------------------- | ||
|
||
While both TypeIs and TypeGuard allow you to define custom type narrowing | ||
functions, they differ in important ways: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The paragraph above is a duplicate. |
||
|
||
- **Type narrowing behavior**: TypeIs narrows the type in both the if and else branches, | ||
whereas TypeGuard narrows only in the if branch. | ||
- **Compatibility requirement**: TypeIs requires that the narrowed type T be compatible | ||
with the input type of the function. TypeGuard does not have this restriction. | ||
- **Type inference**: With TypeIs, the type checker may infer a more precise type by | ||
combining existing type information with T. | ||
|
||
Here's an example demonstrating the behavior with TypeGuard: | ||
|
||
.. code-block:: python | ||
|
||
from typing import TypeGuard, reveal_type | ||
|
||
def is_str(x: object) -> TypeGuard[str]: | ||
return isinstance(x, str) | ||
|
||
def process(x: int | str) -> None: | ||
if is_str(x): | ||
reveal_type(x) # Revealed type is "builtins.str" | ||
print(x.upper()) # ok: x is str | ||
else: | ||
reveal_type(x) # Revealed type is "Union[builtins.int, builtins.str]" | ||
print(x + 1) # ERROR: Unsupported operand types for + ("str" and "int") [operator] | ||
|
||
Generic TypeIs | ||
~~~~~~~~~~~~~~ | ||
|
||
``TypeIs`` functions can also work with generic types: | ||
|
||
.. code-block:: python | ||
|
||
from typing import TypeVar, TypeIs | ||
|
||
T = TypeVar('T') | ||
|
||
def is_two_element_tuple(val: tuple[T, ...]) -> TypeIs[tuple[T, T]]: | ||
return len(val) == 2 | ||
|
||
def process(names: tuple[str, ...]) -> None: | ||
if is_two_element_tuple(names): | ||
reveal_type(names) # Revealed type is 'tuple[str, str]' | ||
else: | ||
reveal_type(names) # Revealed type is 'tuple[str, ...]' | ||
|
||
|
||
TypeIs with Additional Parameters | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
TypeIs functions can accept additional parameters beyond the first. | ||
The type narrowing applies only to the first argument. | ||
|
||
.. code-block:: python | ||
|
||
from typing import Any, TypeVar, Type, reveal_type, TypeIs | ||
|
||
T = TypeVar('T') | ||
|
||
def is_instance_of(val: Any, typ: Type[T]) -> TypeIs[T]: | ||
chelseadz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return isinstance(val, typ) | ||
|
||
def process(x: Any) -> None: | ||
if is_instance_of(x, int): | ||
reveal_type(x) # Revealed type is 'int' or any int subclass | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: The revealed type is just 'int', not any subclass. |
||
print(x + 1) # ok | ||
else: | ||
reveal_type(x) # Revealed type is 'Any' | ||
|
||
TypeIs in Methods | ||
~~~~~~~~~~~~~~~~~ | ||
|
||
A method can also serve as a ``TypeIs`` function. Note that in instance or | ||
class methods, the type narrowing applies to the second parameter | ||
(after ``self`` or ``cls``). | ||
|
||
.. code-block:: python | ||
|
||
class Validator: | ||
def is_valid(self, instance: object) -> TypeIs[str]: | ||
return isinstance(instance, str) | ||
|
||
def process(self, to_validate: object) -> None: | ||
if Validator().is_valid(to_validate): | ||
reveal_type(to_validate) # Revealed type is 'str' | ||
print(to_validate.upper()) # ok: to_validate is str | ||
|
||
|
||
Assignment Expressions with TypeIs | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
You can use the assignment expression operator ``:=`` with ``TypeIs`` to create a new variable and narrow its type simultaneously. | ||
|
||
.. code-block:: python | ||
|
||
from typing import TypeIs, reveal_type | ||
|
||
def is_float(x: object) -> TypeIs[float]: | ||
return isinstance(x, float) | ||
|
||
def main(a: object) -> None: | ||
if is_float(x := a): | ||
reveal_type(x) # Revealed type is 'float' | ||
# x is narrowed to float in this block | ||
print(x + 1.0) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.