[Python-checkins] [3.12] gh-105237: Allow calling `issubclass(X, typing.Protocol)` again (GH-105239) (#105316)

AlexWaygood webhook-mailer at python.org
Mon Jun 5 10:06:32 EDT 2023


https://github.com/python/cpython/commit/51750269cf36901e83a70a1fff9ab93a2e2855fb
commit: 51750269cf36901e83a70a1fff9ab93a2e2855fb
branch: 3.12
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: AlexWaygood <Alex.Waygood at Gmail.com>
date: 2023-06-05T14:06:25Z
summary:

[3.12] gh-105237: Allow calling `issubclass(X, typing.Protocol)` again (GH-105239) (#105316)

gh-105237: Allow calling `issubclass(X, typing.Protocol)` again (GH-105239)
(cherry picked from commit cdfb201bfa35b7c50de5099c6d9078c806851d98)

Co-authored-by: Alex Waygood <Alex.Waygood at Gmail.com>

files:
A Misc/NEWS.d/next/Library/2023-06-02-14-57-11.gh-issue-105239.SAmuuj.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 5480a981ad56..3a2f7393eb46 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -2758,6 +2758,65 @@ def x(self): ...
         with self.assertRaisesRegex(TypeError, only_classes_allowed):
             issubclass(1, BadPG)
 
+    def test_issubclass_and_isinstance_on_Protocol_itself(self):
+        class C:
+            def x(self): pass
+
+        self.assertNotIsSubclass(object, Protocol)
+        self.assertNotIsInstance(object(), Protocol)
+
+        self.assertNotIsSubclass(str, Protocol)
+        self.assertNotIsInstance('foo', Protocol)
+
+        self.assertNotIsSubclass(C, Protocol)
+        self.assertNotIsInstance(C(), Protocol)
+
+        only_classes_allowed = r"issubclass\(\) arg 1 must be a class"
+
+        with self.assertRaisesRegex(TypeError, only_classes_allowed):
+            issubclass(1, Protocol)
+        with self.assertRaisesRegex(TypeError, only_classes_allowed):
+            issubclass('foo', Protocol)
+        with self.assertRaisesRegex(TypeError, only_classes_allowed):
+            issubclass(C(), Protocol)
+
+        T = TypeVar('T')
+
+        @runtime_checkable
+        class EmptyProtocol(Protocol): pass
+
+        @runtime_checkable
+        class SupportsStartsWith(Protocol):
+            def startswith(self, x: str) -> bool: ...
+
+        @runtime_checkable
+        class SupportsX(Protocol[T]):
+            def x(self): ...
+
+        for proto in EmptyProtocol, SupportsStartsWith, SupportsX:
+            with self.subTest(proto=proto.__name__):
+                self.assertIsSubclass(proto, Protocol)
+
+        # gh-105237 / PR #105239:
+        # check that the presence of Protocol subclasses
+        # where `issubclass(X, <subclass>)` evaluates to True
+        # doesn't influence the result of `issubclass(X, Protocol)`
+
+        self.assertIsSubclass(object, EmptyProtocol)
+        self.assertIsInstance(object(), EmptyProtocol)
+        self.assertNotIsSubclass(object, Protocol)
+        self.assertNotIsInstance(object(), Protocol)
+
+        self.assertIsSubclass(str, SupportsStartsWith)
+        self.assertIsInstance('foo', SupportsStartsWith)
+        self.assertNotIsSubclass(str, Protocol)
+        self.assertNotIsInstance('foo', Protocol)
+
+        self.assertIsSubclass(C, SupportsX)
+        self.assertIsInstance(C(), SupportsX)
+        self.assertNotIsSubclass(C, Protocol)
+        self.assertNotIsInstance(C(), Protocol)
+
     def test_protocols_issubclass_non_callable(self):
         class C:
             x = 1
diff --git a/Lib/typing.py b/Lib/typing.py
index 2383d807ec58..06d50306c475 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -1788,6 +1788,8 @@ def __init__(cls, *args, **kwargs):
             )
 
     def __subclasscheck__(cls, other):
+        if cls is Protocol:
+            return type.__subclasscheck__(cls, other)
         if not isinstance(other, type):
             # Same error message as for issubclass(1, int).
             raise TypeError('issubclass() arg 1 must be a class')
@@ -1809,6 +1811,8 @@ def __subclasscheck__(cls, other):
     def __instancecheck__(cls, instance):
         # We need this method for situations where attributes are
         # assigned in __init__.
+        if cls is Protocol:
+            return type.__instancecheck__(cls, instance)
         if not getattr(cls, "_is_protocol", False):
             # i.e., it's a concrete subclass of a protocol
             return super().__instancecheck__(instance)
diff --git a/Misc/NEWS.d/next/Library/2023-06-02-14-57-11.gh-issue-105239.SAmuuj.rst b/Misc/NEWS.d/next/Library/2023-06-02-14-57-11.gh-issue-105239.SAmuuj.rst
new file mode 100644
index 000000000000..35e1b1a217b3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-06-02-14-57-11.gh-issue-105239.SAmuuj.rst
@@ -0,0 +1,2 @@
+Fix longstanding bug where ``issubclass(object, typing.Protocol)`` would
+evaluate to ``True`` in some edge cases. Patch by Alex Waygood.



More information about the Python-checkins mailing list