summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFelix Scherz <felixwscherz@gmail.com>2025-04-16 15:20:35 (GMT)
committerGitHub <noreply@github.com>2025-04-16 15:20:35 (GMT)
commit71af090e24f4df7557e4ad77f26bf0219ef672be (patch)
treec18613c265910f1688a051d5501796806cea593e
parent014c7f90478780b18d0e33d456483178c8dcc665 (diff)
downloadcpython-71af090e24f4df7557e4ad77f26bf0219ef672be.zip
cpython-71af090e24f4df7557e4ad77f26bf0219ef672be.tar.gz
cpython-71af090e24f4df7557e4ad77f26bf0219ef672be.tar.bz2
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>
-rw-r--r--Lib/test/test_typing.py27
-rw-r--r--Lib/typing.py11
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-15-08-39-14.gh-issue-132493.V0gLkU.rst4
3 files changed, 38 insertions, 4 deletions
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 16c5a52..5b05ebe 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -4554,6 +4554,33 @@ class ProtocolTests(BaseTestCase):
)
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):
class DeferredProto(Protocol):
x: DoesNotExist
diff --git a/Lib/typing.py b/Lib/typing.py
index 3678962..245592b 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -2020,10 +2020,13 @@ def _proto_hook(cls, other):
break
# ...or in annotations, if it is a sub-protocol.
- annotations = getattr(base, '__annotations__', {})
- if (isinstance(annotations, collections.abc.Mapping) and
- attr in annotations and
- issubclass(other, Generic) and getattr(other, '_is_protocol', False)):
+ if (
+ issubclass(other, Generic)
+ and getattr(other, "_is_protocol", False)
+ and attr in _lazy_annotationlib.get_annotations(
+ base, format=_lazy_annotationlib.Format.FORWARDREF
+ )
+ ):
break
else:
return NotImplemented
diff --git a/Misc/NEWS.d/next/Library/2025-04-15-08-39-14.gh-issue-132493.V0gLkU.rst b/Misc/NEWS.d/next/Library/2025-04-15-08-39-14.gh-issue-132493.V0gLkU.rst
new file mode 100644
index 0000000..a7f7627
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-15-08-39-14.gh-issue-132493.V0gLkU.rst
@@ -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.