Skip to content

Commit 9048d73

Browse files
authored
gh-74690: typing: Don't unnecessarily call _get_protocol_attrs twice in _ProtocolMeta.__instancecheck__ (#103141)
Speed up `isinstance()` calls against runtime-checkable protocols
1 parent 80163e1 commit 9048d73

File tree

1 file changed

+21
-10
lines changed

1 file changed

+21
-10
lines changed

Lib/typing.py

+21-10
Original file line numberDiff line numberDiff line change
@@ -1931,9 +1931,9 @@ def _get_protocol_attrs(cls):
19311931
return attrs
19321932

19331933

1934-
def _is_callable_members_only(cls):
1934+
def _is_callable_members_only(cls, protocol_attrs):
19351935
# PEP 544 prohibits using issubclass() with protocols that have non-method members.
1936-
return all(callable(getattr(cls, attr, None)) for attr in _get_protocol_attrs(cls))
1936+
return all(callable(getattr(cls, attr, None)) for attr in protocol_attrs)
19371937

19381938

19391939
def _no_init_or_replace_init(self, *args, **kwargs):
@@ -2000,24 +2000,32 @@ class _ProtocolMeta(ABCMeta):
20002000
def __instancecheck__(cls, instance):
20012001
# We need this method for situations where attributes are
20022002
# assigned in __init__.
2003+
is_protocol_cls = getattr(cls, "_is_protocol", False)
20032004
if (
2004-
getattr(cls, '_is_protocol', False) and
2005+
is_protocol_cls and
20052006
not getattr(cls, '_is_runtime_protocol', False) and
20062007
not _allow_reckless_class_checks(depth=2)
20072008
):
20082009
raise TypeError("Instance and class checks can only be used with"
20092010
" @runtime_checkable protocols")
20102011

2011-
if ((not getattr(cls, '_is_protocol', False) or
2012-
_is_callable_members_only(cls)) and
2013-
issubclass(instance.__class__, cls)):
2012+
if not is_protocol_cls and issubclass(instance.__class__, cls):
20142013
return True
2015-
if cls._is_protocol:
2014+
2015+
protocol_attrs = _get_protocol_attrs(cls)
2016+
2017+
if (
2018+
_is_callable_members_only(cls, protocol_attrs)
2019+
and issubclass(instance.__class__, cls)
2020+
):
2021+
return True
2022+
2023+
if is_protocol_cls:
20162024
if all(hasattr(instance, attr) and
20172025
# All *methods* can be blocked by setting them to None.
20182026
(not callable(getattr(cls, attr, None)) or
20192027
getattr(instance, attr) is not None)
2020-
for attr in _get_protocol_attrs(cls)):
2028+
for attr in protocol_attrs):
20212029
return True
20222030
return super().__instancecheck__(instance)
20232031

@@ -2074,7 +2082,10 @@ def _proto_hook(other):
20742082
return NotImplemented
20752083
raise TypeError("Instance and class checks can only be used with"
20762084
" @runtime_checkable protocols")
2077-
if not _is_callable_members_only(cls):
2085+
2086+
protocol_attrs = _get_protocol_attrs(cls)
2087+
2088+
if not _is_callable_members_only(cls, protocol_attrs):
20782089
if _allow_reckless_class_checks():
20792090
return NotImplemented
20802091
raise TypeError("Protocols with non-method members"
@@ -2084,7 +2095,7 @@ def _proto_hook(other):
20842095
raise TypeError('issubclass() arg 1 must be a class')
20852096

20862097
# Second, perform the actual structural compatibility check.
2087-
for attr in _get_protocol_attrs(cls):
2098+
for attr in protocol_attrs:
20882099
for base in other.__mro__:
20892100
# Check if the members appears in the class dictionary...
20902101
if attr in base.__dict__:

0 commit comments

Comments
 (0)