mirror of
https://github.com/python/cpython.git
synced 2025-12-31 04:23:37 +00:00
bpo-43766: Implement PEP 647 (User-Defined Type Guards) in typing.py (#25282)
This commit is contained in:
parent
d92513390a
commit
05ab4b60ab
5 changed files with 175 additions and 0 deletions
|
|
@ -933,6 +933,80 @@ These can be used as types in annotations using ``[]``, each having a unique syn
|
|||
|
||||
.. versionadded:: 3.9
|
||||
|
||||
|
||||
.. data:: TypeGuard
|
||||
|
||||
Special typing form used to annotate the return type of a user-defined
|
||||
type guard 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"::
|
||||
|
||||
def is_str(val: Union[str, float]):
|
||||
# "isinstance" type guard
|
||||
if isinstance(val, str):
|
||||
# Type of ``val`` is narrowed to ``str``
|
||||
...
|
||||
else:
|
||||
# Else, type of ``val`` is narrowed to ``float``.
|
||||
...
|
||||
|
||||
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.
|
||||
|
||||
Using ``-> TypeGuard`` 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``.
|
||||
|
||||
For example::
|
||||
|
||||
def is_str_list(val: List[object]) -> TypeGuard[List[str]]:
|
||||
'''Determines whether all objects in the list are strings'''
|
||||
return all(isinstance(x, str) for x in val)
|
||||
|
||||
def func1(val: List[object]):
|
||||
if is_str_list(val):
|
||||
# Type of ``val`` is narrowed to List[str]
|
||||
print(" ".join(val))
|
||||
else:
|
||||
# Type of ``val`` remains as List[object]
|
||||
print("Not a list of strings!")
|
||||
|
||||
If ``is_str_list`` is a class or instance method, then the type in
|
||||
``TypeGuard`` maps to the type of the second parameter after ``cls`` or
|
||||
``self``.
|
||||
|
||||
In short, the form ``def foo(arg: TypeA) -> TypeGuard[TypeB]: ...``,
|
||||
means that if ``foo(arg)`` returns ``True``, then ``arg`` narrows from
|
||||
``TypeA`` to ``TypeB``.
|
||||
|
||||
.. note::
|
||||
|
||||
``TypeB`` need not be a narrower form of ``TypeA`` -- it can even be a
|
||||
wider form. 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. Even if
|
||||
the type guard function passes type checks, it may still fail at runtime.
|
||||
The type guard function may perform erroneous checks and return wrong
|
||||
booleans. Consequently, the type it promises in ``TypeGuard[TypeB]`` may
|
||||
not hold.
|
||||
|
||||
``TypeGuard`` also works with type variables. For more information, see
|
||||
:pep:`647` (User-Defined Type Guards).
|
||||
|
||||
.. versionadded:: 3.10
|
||||
|
||||
|
||||
Building generic types
|
||||
""""""""""""""""""""""
|
||||
|
||||
|
|
|
|||
|
|
@ -743,6 +743,16 @@ See :pep:`613` for more details.
|
|||
|
||||
(Contributed by Mikhail Golubev in :issue:`41923`.)
|
||||
|
||||
PEP 647: User-Defined Type Guards
|
||||
---------------------------------
|
||||
|
||||
:data:`TypeGuard` has been added to the :mod:`typing` module to annotate
|
||||
type guard functions and improve information provided to static type checkers
|
||||
during type narrowing. For more information, please see :data:`TypeGuard`\ 's
|
||||
documentation, and :pep:`647`.
|
||||
|
||||
(Contributed by Ken Jin and Guido van Rossum in :issue:`43766`.
|
||||
PEP written by Eric Traut.)
|
||||
|
||||
Other Language Changes
|
||||
======================
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
from typing import Annotated, ForwardRef
|
||||
from typing import TypeAlias
|
||||
from typing import ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs
|
||||
from typing import TypeGuard
|
||||
import abc
|
||||
import typing
|
||||
import weakref
|
||||
|
|
@ -4377,6 +4378,45 @@ def test_valid_uses(self):
|
|||
self.assertEqual(C4.__parameters__, (T, P))
|
||||
|
||||
|
||||
class TypeGuardTests(BaseTestCase):
|
||||
def test_basics(self):
|
||||
TypeGuard[int] # OK
|
||||
|
||||
def foo(arg) -> TypeGuard[int]: ...
|
||||
self.assertEqual(gth(foo), {'return': TypeGuard[int]})
|
||||
|
||||
def test_repr(self):
|
||||
self.assertEqual(repr(TypeGuard), 'typing.TypeGuard')
|
||||
cv = TypeGuard[int]
|
||||
self.assertEqual(repr(cv), 'typing.TypeGuard[int]')
|
||||
cv = TypeGuard[Employee]
|
||||
self.assertEqual(repr(cv), 'typing.TypeGuard[%s.Employee]' % __name__)
|
||||
cv = TypeGuard[tuple[int]]
|
||||
self.assertEqual(repr(cv), 'typing.TypeGuard[tuple[int]]')
|
||||
|
||||
def test_cannot_subclass(self):
|
||||
with self.assertRaises(TypeError):
|
||||
class C(type(TypeGuard)):
|
||||
pass
|
||||
with self.assertRaises(TypeError):
|
||||
class C(type(TypeGuard[int])):
|
||||
pass
|
||||
|
||||
def test_cannot_init(self):
|
||||
with self.assertRaises(TypeError):
|
||||
TypeGuard()
|
||||
with self.assertRaises(TypeError):
|
||||
type(TypeGuard)()
|
||||
with self.assertRaises(TypeError):
|
||||
type(TypeGuard[Optional[int]])()
|
||||
|
||||
def test_no_isinstance(self):
|
||||
with self.assertRaises(TypeError):
|
||||
isinstance(1, TypeGuard[int])
|
||||
with self.assertRaises(TypeError):
|
||||
issubclass(int, TypeGuard)
|
||||
|
||||
|
||||
class AllTests(BaseTestCase):
|
||||
"""Tests for __all__."""
|
||||
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@
|
|||
'Text',
|
||||
'TYPE_CHECKING',
|
||||
'TypeAlias',
|
||||
'TypeGuard',
|
||||
]
|
||||
|
||||
# The pseudo-submodules 're' and 'io' are part of the public
|
||||
|
|
@ -567,6 +568,54 @@ def Concatenate(self, parameters):
|
|||
return _ConcatenateGenericAlias(self, parameters)
|
||||
|
||||
|
||||
@_SpecialForm
|
||||
def TypeGuard(self, parameters):
|
||||
"""Special typing form used to annotate the return type of a user-defined
|
||||
type guard 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".
|
||||
|
||||
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.
|
||||
|
||||
Using ``-> TypeGuard`` 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``.
|
||||
|
||||
For example::
|
||||
|
||||
def is_str(val: Union[str, float]):
|
||||
# "isinstance" type guard
|
||||
if isinstance(val, str):
|
||||
# Type of ``val`` is narrowed to ``str``
|
||||
...
|
||||
else:
|
||||
# Else, type of ``val`` is narrowed to ``float``.
|
||||
...
|
||||
|
||||
Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower
|
||||
form of ``TypeA`` (it can even be a wider form) and this may lead to
|
||||
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.
|
||||
|
||||
``TypeGuard`` also works with type variables. For more information, see
|
||||
PEP 647 (User-Defined Type Guards).
|
||||
"""
|
||||
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."""
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
Implement :pep:`647` in the :mod:`typing` module by adding
|
||||
:data:`TypeGuard`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue