[3.12] gh-105974: Revert unintentional behaviour change for protocols with non-callable members and custom __subclasshook__ methods (GH-105976) (#106032)

gh-105974: Revert unintentional behaviour change for protocols with non-callable members and custom `__subclasshook__` methods (GH-105976)
(cherry picked from commit 9499b0f138)

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
Miss Islington (bot) 2023-06-23 08:26:37 -07:00 committed by GitHub
parent 1ffcd49be2
commit 7d6ee298e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 79 additions and 32 deletions

View file

@ -1822,14 +1822,17 @@ class _ProtocolMeta(ABCMeta):
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')
if (
getattr(cls, '_is_protocol', False)
and not _allow_reckless_class_checks()
):
if not cls.__callable_proto_members_only__:
if not isinstance(other, type):
# Same error message as for issubclass(1, int).
raise TypeError('issubclass() arg 1 must be a class')
if (
not cls.__callable_proto_members_only__
and cls.__dict__.get("__subclasshook__") is _proto_hook
):
raise TypeError(
"Protocols with non-method members don't support issubclass()"
)
@ -1873,6 +1876,30 @@ class _ProtocolMeta(ABCMeta):
return False
@classmethod
def _proto_hook(cls, other):
if not cls.__dict__.get('_is_protocol', False):
return NotImplemented
for attr in cls.__protocol_attrs__:
for base in other.__mro__:
# Check if the members appears in the class dictionary...
if attr in base.__dict__:
if base.__dict__[attr] is None:
return NotImplemented
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)):
break
else:
return NotImplemented
return True
class Protocol(Generic, metaclass=_ProtocolMeta):
"""Base class for protocol classes.
@ -1918,37 +1945,11 @@ class Protocol(Generic, metaclass=_ProtocolMeta):
cls._is_protocol = any(b is Protocol for b in cls.__bases__)
# Set (or override) the protocol subclass hook.
def _proto_hook(other):
if not cls.__dict__.get('_is_protocol', False):
return NotImplemented
for attr in cls.__protocol_attrs__:
for base in other.__mro__:
# Check if the members appears in the class dictionary...
if attr in base.__dict__:
if base.__dict__[attr] is None:
return NotImplemented
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)):
break
else:
return NotImplemented
return True
if '__subclasshook__' not in cls.__dict__:
cls.__subclasshook__ = _proto_hook
# We have nothing more to do for non-protocols...
if not cls._is_protocol:
return
# ... otherwise prohibit instantiation.
if cls.__init__ is Protocol.__init__:
# Prohibit instantiation for protocol classes
if cls._is_protocol and cls.__init__ is Protocol.__init__:
cls.__init__ = _no_init_or_replace_init