Skip to content

Commit 5175026

Browse files
[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 cdfb201) Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
1 parent 6d03541 commit 5175026

File tree

3 files changed

+65
-0
lines changed

3 files changed

+65
-0
lines changed

Lib/test/test_typing.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2758,6 +2758,65 @@ def x(self): ...
27582758
with self.assertRaisesRegex(TypeError, only_classes_allowed):
27592759
issubclass(1, BadPG)
27602760

2761+
def test_issubclass_and_isinstance_on_Protocol_itself(self):
2762+
class C:
2763+
def x(self): pass
2764+
2765+
self.assertNotIsSubclass(object, Protocol)
2766+
self.assertNotIsInstance(object(), Protocol)
2767+
2768+
self.assertNotIsSubclass(str, Protocol)
2769+
self.assertNotIsInstance('foo', Protocol)
2770+
2771+
self.assertNotIsSubclass(C, Protocol)
2772+
self.assertNotIsInstance(C(), Protocol)
2773+
2774+
only_classes_allowed = r"issubclass\(\) arg 1 must be a class"
2775+
2776+
with self.assertRaisesRegex(TypeError, only_classes_allowed):
2777+
issubclass(1, Protocol)
2778+
with self.assertRaisesRegex(TypeError, only_classes_allowed):
2779+
issubclass('foo', Protocol)
2780+
with self.assertRaisesRegex(TypeError, only_classes_allowed):
2781+
issubclass(C(), Protocol)
2782+
2783+
T = TypeVar('T')
2784+
2785+
@runtime_checkable
2786+
class EmptyProtocol(Protocol): pass
2787+
2788+
@runtime_checkable
2789+
class SupportsStartsWith(Protocol):
2790+
def startswith(self, x: str) -> bool: ...
2791+
2792+
@runtime_checkable
2793+
class SupportsX(Protocol[T]):
2794+
def x(self): ...
2795+
2796+
for proto in EmptyProtocol, SupportsStartsWith, SupportsX:
2797+
with self.subTest(proto=proto.__name__):
2798+
self.assertIsSubclass(proto, Protocol)
2799+
2800+
# gh-105237 / PR #105239:
2801+
# check that the presence of Protocol subclasses
2802+
# where `issubclass(X, <subclass>)` evaluates to True
2803+
# doesn't influence the result of `issubclass(X, Protocol)`
2804+
2805+
self.assertIsSubclass(object, EmptyProtocol)
2806+
self.assertIsInstance(object(), EmptyProtocol)
2807+
self.assertNotIsSubclass(object, Protocol)
2808+
self.assertNotIsInstance(object(), Protocol)
2809+
2810+
self.assertIsSubclass(str, SupportsStartsWith)
2811+
self.assertIsInstance('foo', SupportsStartsWith)
2812+
self.assertNotIsSubclass(str, Protocol)
2813+
self.assertNotIsInstance('foo', Protocol)
2814+
2815+
self.assertIsSubclass(C, SupportsX)
2816+
self.assertIsInstance(C(), SupportsX)
2817+
self.assertNotIsSubclass(C, Protocol)
2818+
self.assertNotIsInstance(C(), Protocol)
2819+
27612820
def test_protocols_issubclass_non_callable(self):
27622821
class C:
27632822
x = 1

Lib/typing.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1788,6 +1788,8 @@ def __init__(cls, *args, **kwargs):
17881788
)
17891789

17901790
def __subclasscheck__(cls, other):
1791+
if cls is Protocol:
1792+
return type.__subclasscheck__(cls, other)
17911793
if not isinstance(other, type):
17921794
# Same error message as for issubclass(1, int).
17931795
raise TypeError('issubclass() arg 1 must be a class')
@@ -1809,6 +1811,8 @@ def __subclasscheck__(cls, other):
18091811
def __instancecheck__(cls, instance):
18101812
# We need this method for situations where attributes are
18111813
# assigned in __init__.
1814+
if cls is Protocol:
1815+
return type.__instancecheck__(cls, instance)
18121816
if not getattr(cls, "_is_protocol", False):
18131817
# i.e., it's a concrete subclass of a protocol
18141818
return super().__instancecheck__(instance)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix longstanding bug where ``issubclass(object, typing.Protocol)`` would
2+
evaluate to ``True`` in some edge cases. Patch by Alex Waygood.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy