Skip to content

Commit 71af090

Browse files
felixscherzJelleZijlstrasobolevnAlexWaygood
authored
gh-132493: lazy evaluation of annotations in typing._proto_hook (#132534)
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com> Co-authored-by: sobolevn <mail@sobolevn.me> Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
1 parent 014c7f9 commit 71af090

File tree

3 files changed

+38
-4
lines changed

3 files changed

+38
-4
lines changed

Diff for: Lib/test/test_typing.py

+27
Original file line numberDiff line numberDiff line change
@@ -4554,6 +4554,33 @@ class Commentable(Protocol):
45544554
)
45554555
self.assertIs(type(exc.__cause__), CustomError)
45564556

4557+
def test_isinstance_with_deferred_evaluation_of_annotations(self):
4558+
@runtime_checkable
4559+
class P(Protocol):
4560+
def meth(self):
4561+
...
4562+
4563+
class DeferredClass:
4564+
x: undefined
4565+
4566+
class DeferredClassImplementingP:
4567+
x: undefined | int
4568+
4569+
def __init__(self):
4570+
self.x = 0
4571+
4572+
def meth(self):
4573+
...
4574+
4575+
# override meth with a non-method attribute to make it part of __annotations__ instead of __dict__
4576+
class SubProtocol(P, Protocol):
4577+
meth: undefined
4578+
4579+
4580+
self.assertIsSubclass(SubProtocol, P)
4581+
self.assertNotIsInstance(DeferredClass(), P)
4582+
self.assertIsInstance(DeferredClassImplementingP(), P)
4583+
45574584
def test_deferred_evaluation_of_annotations(self):
45584585
class DeferredProto(Protocol):
45594586
x: DoesNotExist

Diff for: Lib/typing.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -2020,10 +2020,13 @@ def _proto_hook(cls, other):
20202020
break
20212021

20222022
# ...or in annotations, if it is a sub-protocol.
2023-
annotations = getattr(base, '__annotations__', {})
2024-
if (isinstance(annotations, collections.abc.Mapping) and
2025-
attr in annotations and
2026-
issubclass(other, Generic) and getattr(other, '_is_protocol', False)):
2023+
if (
2024+
issubclass(other, Generic)
2025+
and getattr(other, "_is_protocol", False)
2026+
and attr in _lazy_annotationlib.get_annotations(
2027+
base, format=_lazy_annotationlib.Format.FORWARDREF
2028+
)
2029+
):
20272030
break
20282031
else:
20292032
return NotImplemented
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:class:`typing.Protocol` now uses :func:`annotationlib.get_annotations` when
2+
checking whether or not an instance implements the protocol with
3+
:func:`isinstance`. This enables support for ``isinstance`` checks against
4+
classes with deferred annotations.

0 commit comments

Comments
 (0)