Skip to content

Commit 3246688

Browse files
authored
gh-74690: typing: Call _get_protocol_attrs and _callable_members_only at protocol class creation time, not during isinstance() checks (#103160)
1 parent c396b6d commit 3246688

File tree

1 file changed

+14
-11
lines changed

1 file changed

+14
-11
lines changed

Lib/typing.py

+14-11
Original file line numberDiff line numberDiff line change
@@ -1905,7 +1905,8 @@ class _TypingEllipsis:
19051905

19061906
_TYPING_INTERNALS = frozenset({
19071907
'__parameters__', '__orig_bases__', '__orig_class__',
1908-
'_is_protocol', '_is_runtime_protocol'
1908+
'_is_protocol', '_is_runtime_protocol', '__protocol_attrs__',
1909+
'__callable_proto_members_only__',
19091910
})
19101911

19111912
_SPECIAL_NAMES = frozenset({
@@ -1935,11 +1936,6 @@ def _get_protocol_attrs(cls):
19351936
return attrs
19361937

19371938

1938-
def _is_callable_members_only(cls, protocol_attrs):
1939-
# PEP 544 prohibits using issubclass() with protocols that have non-method members.
1940-
return all(callable(getattr(cls, attr, None)) for attr in protocol_attrs)
1941-
1942-
19431939
def _no_init_or_replace_init(self, *args, **kwargs):
19441940
cls = type(self)
19451941

@@ -2012,6 +2008,15 @@ def _lazy_load_getattr_static():
20122008
class _ProtocolMeta(ABCMeta):
20132009
# This metaclass is really unfortunate and exists only because of
20142010
# the lack of __instancehook__.
2011+
def __init__(cls, *args, **kwargs):
2012+
super().__init__(*args, **kwargs)
2013+
cls.__protocol_attrs__ = _get_protocol_attrs(cls)
2014+
# PEP 544 prohibits using issubclass()
2015+
# with protocols that have non-method members.
2016+
cls.__callable_proto_members_only__ = all(
2017+
callable(getattr(cls, attr, None)) for attr in cls.__protocol_attrs__
2018+
)
2019+
20152020
def __instancecheck__(cls, instance):
20162021
# We need this method for situations where attributes are
20172022
# assigned in __init__.
@@ -2029,7 +2034,7 @@ def __instancecheck__(cls, instance):
20292034

20302035
if is_protocol_cls:
20312036
getattr_static = _lazy_load_getattr_static()
2032-
for attr in _get_protocol_attrs(cls):
2037+
for attr in cls.__protocol_attrs__:
20332038
try:
20342039
val = getattr_static(instance, attr)
20352040
except AttributeError:
@@ -2095,9 +2100,7 @@ def _proto_hook(other):
20952100
raise TypeError("Instance and class checks can only be used with"
20962101
" @runtime_checkable protocols")
20972102

2098-
protocol_attrs = _get_protocol_attrs(cls)
2099-
2100-
if not _is_callable_members_only(cls, protocol_attrs):
2103+
if not cls.__callable_proto_members_only__ :
21012104
if _allow_reckless_class_checks():
21022105
return NotImplemented
21032106
raise TypeError("Protocols with non-method members"
@@ -2107,7 +2110,7 @@ def _proto_hook(other):
21072110
raise TypeError('issubclass() arg 1 must be a class')
21082111

21092112
# Second, perform the actual structural compatibility check.
2110-
for attr in protocol_attrs:
2113+
for attr in cls.__protocol_attrs__:
21112114
for base in other.__mro__:
21122115
# Check if the members appears in the class dictionary...
21132116
if attr in base.__dict__:

0 commit comments

Comments
 (0)