mirror of
https://github.com/python/cpython.git
synced 2025-08-09 19:38:42 +00:00
[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 cdfb201bfa
)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
6d0354167f
commit
51750269cf
3 changed files with 65 additions and 0 deletions
|
@ -2758,6 +2758,65 @@ class ProtocolTests(BaseTestCase):
|
||||||
with self.assertRaisesRegex(TypeError, only_classes_allowed):
|
with self.assertRaisesRegex(TypeError, only_classes_allowed):
|
||||||
issubclass(1, BadPG)
|
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):
|
def test_protocols_issubclass_non_callable(self):
|
||||||
class C:
|
class C:
|
||||||
x = 1
|
x = 1
|
||||||
|
|
|
@ -1788,6 +1788,8 @@ class _ProtocolMeta(ABCMeta):
|
||||||
)
|
)
|
||||||
|
|
||||||
def __subclasscheck__(cls, other):
|
def __subclasscheck__(cls, other):
|
||||||
|
if cls is Protocol:
|
||||||
|
return type.__subclasscheck__(cls, other)
|
||||||
if not isinstance(other, type):
|
if not isinstance(other, type):
|
||||||
# Same error message as for issubclass(1, int).
|
# Same error message as for issubclass(1, int).
|
||||||
raise TypeError('issubclass() arg 1 must be a class')
|
raise TypeError('issubclass() arg 1 must be a class')
|
||||||
|
@ -1809,6 +1811,8 @@ class _ProtocolMeta(ABCMeta):
|
||||||
def __instancecheck__(cls, instance):
|
def __instancecheck__(cls, instance):
|
||||||
# We need this method for situations where attributes are
|
# We need this method for situations where attributes are
|
||||||
# assigned in __init__.
|
# assigned in __init__.
|
||||||
|
if cls is Protocol:
|
||||||
|
return type.__instancecheck__(cls, instance)
|
||||||
if not getattr(cls, "_is_protocol", False):
|
if not getattr(cls, "_is_protocol", False):
|
||||||
# i.e., it's a concrete subclass of a protocol
|
# i.e., it's a concrete subclass of a protocol
|
||||||
return super().__instancecheck__(instance)
|
return super().__instancecheck__(instance)
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Fix longstanding bug where ``issubclass(object, typing.Protocol)`` would
|
||||||
|
evaluate to ``True`` in some edge cases. Patch by Alex Waygood.
|
Loading…
Add table
Add a link
Reference in a new issue