gh-74690: typing: Call _get_protocol_attrs
and _callable_members_only
at protocol class creation time, not during isinstance()
checks (#103160)
This commit is contained in:
parent
c396b6ddf3
commit
3246688918
@ -1905,7 +1905,8 @@ class _TypingEllipsis:
|
|||||||
|
|
||||||
_TYPING_INTERNALS = frozenset({
|
_TYPING_INTERNALS = frozenset({
|
||||||
'__parameters__', '__orig_bases__', '__orig_class__',
|
'__parameters__', '__orig_bases__', '__orig_class__',
|
||||||
'_is_protocol', '_is_runtime_protocol'
|
'_is_protocol', '_is_runtime_protocol', '__protocol_attrs__',
|
||||||
|
'__callable_proto_members_only__',
|
||||||
})
|
})
|
||||||
|
|
||||||
_SPECIAL_NAMES = frozenset({
|
_SPECIAL_NAMES = frozenset({
|
||||||
@ -1935,11 +1936,6 @@ def _get_protocol_attrs(cls):
|
|||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
def _is_callable_members_only(cls, protocol_attrs):
|
|
||||||
# PEP 544 prohibits using issubclass() with protocols that have non-method members.
|
|
||||||
return all(callable(getattr(cls, attr, None)) for attr in protocol_attrs)
|
|
||||||
|
|
||||||
|
|
||||||
def _no_init_or_replace_init(self, *args, **kwargs):
|
def _no_init_or_replace_init(self, *args, **kwargs):
|
||||||
cls = type(self)
|
cls = type(self)
|
||||||
|
|
||||||
@ -2012,6 +2008,15 @@ _cleanups.append(_lazy_load_getattr_static.cache_clear)
|
|||||||
class _ProtocolMeta(ABCMeta):
|
class _ProtocolMeta(ABCMeta):
|
||||||
# This metaclass is really unfortunate and exists only because of
|
# This metaclass is really unfortunate and exists only because of
|
||||||
# the lack of __instancehook__.
|
# the lack of __instancehook__.
|
||||||
|
def __init__(cls, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
cls.__protocol_attrs__ = _get_protocol_attrs(cls)
|
||||||
|
# PEP 544 prohibits using issubclass()
|
||||||
|
# with protocols that have non-method members.
|
||||||
|
cls.__callable_proto_members_only__ = all(
|
||||||
|
callable(getattr(cls, attr, None)) for attr in cls.__protocol_attrs__
|
||||||
|
)
|
||||||
|
|
||||||
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__.
|
||||||
@ -2029,7 +2034,7 @@ class _ProtocolMeta(ABCMeta):
|
|||||||
|
|
||||||
if is_protocol_cls:
|
if is_protocol_cls:
|
||||||
getattr_static = _lazy_load_getattr_static()
|
getattr_static = _lazy_load_getattr_static()
|
||||||
for attr in _get_protocol_attrs(cls):
|
for attr in cls.__protocol_attrs__:
|
||||||
try:
|
try:
|
||||||
val = getattr_static(instance, attr)
|
val = getattr_static(instance, attr)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@ -2095,9 +2100,7 @@ class Protocol(Generic, metaclass=_ProtocolMeta):
|
|||||||
raise TypeError("Instance and class checks can only be used with"
|
raise TypeError("Instance and class checks can only be used with"
|
||||||
" @runtime_checkable protocols")
|
" @runtime_checkable protocols")
|
||||||
|
|
||||||
protocol_attrs = _get_protocol_attrs(cls)
|
if not cls.__callable_proto_members_only__ :
|
||||||
|
|
||||||
if not _is_callable_members_only(cls, protocol_attrs):
|
|
||||||
if _allow_reckless_class_checks():
|
if _allow_reckless_class_checks():
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
raise TypeError("Protocols with non-method members"
|
raise TypeError("Protocols with non-method members"
|
||||||
@ -2107,7 +2110,7 @@ class Protocol(Generic, metaclass=_ProtocolMeta):
|
|||||||
raise TypeError('issubclass() arg 1 must be a class')
|
raise TypeError('issubclass() arg 1 must be a class')
|
||||||
|
|
||||||
# Second, perform the actual structural compatibility check.
|
# Second, perform the actual structural compatibility check.
|
||||||
for attr in protocol_attrs:
|
for attr in cls.__protocol_attrs__:
|
||||||
for base in other.__mro__:
|
for base in other.__mro__:
|
||||||
# Check if the members appears in the class dictionary...
|
# Check if the members appears in the class dictionary...
|
||||||
if attr in base.__dict__:
|
if attr in base.__dict__:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user