https://github.com/python/cpython/commit/71af090e24f4df7557e4ad77f26bf0219ef672be
commit: 71af090e24f4df7557e4ad77f26bf0219ef672be
branch: main
author: Felix Scherz <felixwsch...@gmail.com>
committer: JelleZijlstra <jelle.zijls...@gmail.com>
date: 2025-04-16T08:20:35-07:00
summary:

gh-132493: lazy evaluation of annotations in `typing._proto_hook` (#132534)

Co-authored-by: Jelle Zijlstra <jelle.zijls...@gmail.com>
Co-authored-by: sobolevn <m...@sobolevn.me>
Co-authored-by: Alex Waygood <alex.wayg...@gmail.com>

files:
A Misc/NEWS.d/next/Library/2025-04-15-08-39-14.gh-issue-132493.V0gLkU.rst
M Lib/test/test_typing.py
M Lib/typing.py

diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 16c5a5204da102..5b05ebe8702015 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -4554,6 +4554,33 @@ class Commentable(Protocol):
         )
         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 36789624d2f57a..245592b5678957 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 00000000000000..a7f762703642a7
--- /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.

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to