mirror of
https://github.com/python/cpython.git
synced 2025-11-25 12:44:13 +00:00
gh-117516: Implement typing.TypeIs (#117517)
See PEP 742. Co-authored-by: Carl Meyer <carl@oddbird.net> Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
This commit is contained in:
parent
57183241af
commit
f2132fcd2a
5 changed files with 235 additions and 38 deletions
|
|
@ -153,6 +153,7 @@ __all__ = [
|
|||
'TYPE_CHECKING',
|
||||
'TypeAlias',
|
||||
'TypeGuard',
|
||||
'TypeIs',
|
||||
'TypeAliasType',
|
||||
'Unpack',
|
||||
]
|
||||
|
|
@ -818,28 +819,31 @@ def Concatenate(self, parameters):
|
|||
|
||||
@_SpecialForm
|
||||
def TypeGuard(self, parameters):
|
||||
"""Special typing construct for marking user-defined type guard functions.
|
||||
"""Special typing construct for marking user-defined type predicate functions.
|
||||
|
||||
``TypeGuard`` can be used to annotate the return type of a user-defined
|
||||
type guard function. ``TypeGuard`` only accepts a single type argument.
|
||||
type predicate function. ``TypeGuard`` only accepts a single type argument.
|
||||
At runtime, functions marked this way should return a boolean.
|
||||
|
||||
``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static
|
||||
type checkers to determine a more precise type of an expression within a
|
||||
program's code flow. Usually type narrowing is done by analyzing
|
||||
conditional code flow and applying the narrowing to a block of code. The
|
||||
conditional expression here is sometimes referred to as a "type guard".
|
||||
conditional expression here is sometimes referred to as a "type predicate".
|
||||
|
||||
Sometimes it would be convenient to use a user-defined boolean function
|
||||
as a type guard. Such a function should use ``TypeGuard[...]`` as its
|
||||
return type to alert static type checkers to this intention.
|
||||
as a type predicate. Such a function should use ``TypeGuard[...]`` or
|
||||
``TypeIs[...]`` as its return type to alert static type checkers to
|
||||
this intention. ``TypeGuard`` should be used over ``TypeIs`` when narrowing
|
||||
from an incompatible type (e.g., ``list[object]`` to ``list[int]``) or when
|
||||
the function does not return ``True`` for all instances of the narrowed type.
|
||||
|
||||
Using ``-> TypeGuard`` tells the static type checker that for a given
|
||||
function:
|
||||
Using ``-> TypeGuard[NarrowedType]`` tells the static type checker that
|
||||
for a given function:
|
||||
|
||||
1. The return value is a boolean.
|
||||
2. If the return value is ``True``, the type of its argument
|
||||
is the type inside ``TypeGuard``.
|
||||
is ``NarrowedType``.
|
||||
|
||||
For example::
|
||||
|
||||
|
|
@ -860,7 +864,7 @@ def TypeGuard(self, parameters):
|
|||
type-unsafe results. The main reason is to allow for things like
|
||||
narrowing ``list[object]`` to ``list[str]`` even though the latter is not
|
||||
a subtype of the former, since ``list`` is invariant. The responsibility of
|
||||
writing type-safe type guards is left to the user.
|
||||
writing type-safe type predicates is left to the user.
|
||||
|
||||
``TypeGuard`` also works with type variables. For more information, see
|
||||
PEP 647 (User-Defined Type Guards).
|
||||
|
|
@ -869,6 +873,75 @@ def TypeGuard(self, parameters):
|
|||
return _GenericAlias(self, (item,))
|
||||
|
||||
|
||||
@_SpecialForm
|
||||
def TypeIs(self, parameters):
|
||||
"""Special typing construct for marking user-defined type predicate functions.
|
||||
|
||||
``TypeIs`` can be used to annotate the return type of a user-defined
|
||||
type predicate function. ``TypeIs`` only accepts a single type argument.
|
||||
At runtime, functions marked this way should return a boolean and accept
|
||||
at least one argument.
|
||||
|
||||
``TypeIs`` aims to benefit *type narrowing* -- a technique used by static
|
||||
type checkers to determine a more precise type of an expression within a
|
||||
program's code flow. Usually type narrowing is done by analyzing
|
||||
conditional code flow and applying the narrowing to a block of code. The
|
||||
conditional expression here is sometimes referred to as a "type predicate".
|
||||
|
||||
Sometimes it would be convenient to use a user-defined boolean function
|
||||
as a type predicate. Such a function should use ``TypeIs[...]`` or
|
||||
``TypeGuard[...]`` as its return type to alert static type checkers to
|
||||
this intention. ``TypeIs`` usually has more intuitive behavior than
|
||||
``TypeGuard``, but it cannot be used when the input and output types
|
||||
are incompatible (e.g., ``list[object]`` to ``list[int]``) or when the
|
||||
function does not return ``True`` for all instances of the narrowed type.
|
||||
|
||||
Using ``-> TypeIs[NarrowedType]`` tells the static type checker that for
|
||||
a given function:
|
||||
|
||||
1. The return value is a boolean.
|
||||
2. If the return value is ``True``, the type of its argument
|
||||
is the intersection of the argument's original type and
|
||||
``NarrowedType``.
|
||||
3. If the return value is ``False``, the type of its argument
|
||||
is narrowed to exclude ``NarrowedType``.
|
||||
|
||||
For example::
|
||||
|
||||
from typing import assert_type, final, TypeIs
|
||||
|
||||
class Parent: pass
|
||||
class Child(Parent): pass
|
||||
@final
|
||||
class Unrelated: pass
|
||||
|
||||
def is_parent(val: object) -> TypeIs[Parent]:
|
||||
return isinstance(val, Parent)
|
||||
|
||||
def run(arg: Child | Unrelated):
|
||||
if is_parent(arg):
|
||||
# Type of ``arg`` is narrowed to the intersection
|
||||
# of ``Parent`` and ``Child``, which is equivalent to
|
||||
# ``Child``.
|
||||
assert_type(arg, Child)
|
||||
else:
|
||||
# Type of ``arg`` is narrowed to exclude ``Parent``,
|
||||
# so only ``Unrelated`` is left.
|
||||
assert_type(arg, Unrelated)
|
||||
|
||||
The type inside ``TypeIs`` must be consistent with the type of the
|
||||
function's argument; if it is not, static type checkers will raise
|
||||
an error. An incorrectly written ``TypeIs`` function can lead to
|
||||
unsound behavior in the type system; it is the user's responsibility
|
||||
to write such functions in a type-safe manner.
|
||||
|
||||
``TypeIs`` also works with type variables. For more information, see
|
||||
PEP 742 (Narrowing types with ``TypeIs``).
|
||||
"""
|
||||
item = _type_check(parameters, f'{self} accepts only single type.')
|
||||
return _GenericAlias(self, (item,))
|
||||
|
||||
|
||||
class ForwardRef(_Final, _root=True):
|
||||
"""Internal wrapper to hold a forward reference."""
|
||||
|
||||
|
|
@ -1241,11 +1314,12 @@ class _GenericAlias(_BaseGenericAlias, _root=True):
|
|||
# A = Callable[[], None] # _CallableGenericAlias
|
||||
# B = Callable[[T], None] # _CallableGenericAlias
|
||||
# C = B[int] # _CallableGenericAlias
|
||||
# * Parameterized `Final`, `ClassVar` and `TypeGuard`:
|
||||
# * Parameterized `Final`, `ClassVar`, `TypeGuard`, and `TypeIs`:
|
||||
# # All _GenericAlias
|
||||
# Final[int]
|
||||
# ClassVar[float]
|
||||
# TypeVar[bool]
|
||||
# TypeGuard[bool]
|
||||
# TypeIs[range]
|
||||
|
||||
def __init__(self, origin, args, *, inst=True, name=None):
|
||||
super().__init__(origin, inst=inst, name=name)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue