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>
This commit is contained in:
Felix Scherz 2025-04-16 17:20:35 +02:00 committed by GitHub
parent 014c7f9047
commit 71af090e24
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 38 additions and 4 deletions

View file

@ -4554,6 +4554,33 @@ class ProtocolTests(BaseTestCase):
) )
self.assertIs(type(exc.__cause__), CustomError) self.assertIs(type(exc.__cause__), CustomError)
def test_isinstance_with_deferred_evaluation_of_annotations(self):
@runtime_checkable
class P(Protocol):
def meth(self):
...
class DeferredClass:
x: undefined
class DeferredClassImplementingP:
x: undefined | int
def __init__(self):
self.x = 0
def meth(self):
...
# override meth with a non-method attribute to make it part of __annotations__ instead of __dict__
class SubProtocol(P, Protocol):
meth: undefined
self.assertIsSubclass(SubProtocol, P)
self.assertNotIsInstance(DeferredClass(), P)
self.assertIsInstance(DeferredClassImplementingP(), P)
def test_deferred_evaluation_of_annotations(self): def test_deferred_evaluation_of_annotations(self):
class DeferredProto(Protocol): class DeferredProto(Protocol):
x: DoesNotExist x: DoesNotExist

View file

@ -2020,10 +2020,13 @@ def _proto_hook(cls, other):
break break
# ...or in annotations, if it is a sub-protocol. # ...or in annotations, if it is a sub-protocol.
annotations = getattr(base, '__annotations__', {}) if (
if (isinstance(annotations, collections.abc.Mapping) and issubclass(other, Generic)
attr in annotations and and getattr(other, "_is_protocol", False)
issubclass(other, Generic) and getattr(other, '_is_protocol', False)): and attr in _lazy_annotationlib.get_annotations(
base, format=_lazy_annotationlib.Format.FORWARDREF
)
):
break break
else: else:
return NotImplemented return NotImplemented

View file

@ -0,0 +1,4 @@
:class:`typing.Protocol` now uses :func:`annotationlib.get_annotations` when
checking whether or not an instance implements the protocol with
:func:`isinstance`. This enables support for ``isinstance`` checks against
classes with deferred annotations.