summaryrefslogtreecommitdiffstats
path: root/Lib/typing.py
diff options
context:
space:
mode:
authorJelle Zijlstra <jelle.zijlstra@gmail.com>2024-04-09 10:50:37 (GMT)
committerGitHub <noreply@github.com>2024-04-09 10:50:37 (GMT)
commitf2132fcd2a6da7b2b86e80189fa009ce1d2c753b (patch)
tree60af726eaededf67441d206a154b2bc1cec0e0f7 /Lib/typing.py
parent57183241af76bf33e44d886a733f799d20fc680c (diff)
downloadcpython-f2132fcd2a6da7b2b86e80189fa009ce1d2c753b.zip
cpython-f2132fcd2a6da7b2b86e80189fa009ce1d2c753b.tar.gz
cpython-f2132fcd2a6da7b2b86e80189fa009ce1d2c753b.tar.bz2
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>
Diffstat (limited to 'Lib/typing.py')
-rw-r--r--Lib/typing.py96
1 files changed, 85 insertions, 11 deletions
diff --git a/Lib/typing.py b/Lib/typing.py
index d8e4ee3..231492c 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -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)