Skip to content

Commit

Permalink
pythongh-74690: typing: Don't unnecessarily call `_get_protocol_attrs…
Browse files Browse the repository at this point in the history
…` twice in `_ProtocolMeta.__instancecheck__`
  • Loading branch information
AlexWaygood committed Mar 30, 2023
1 parent 01a49d1 commit 51ca233
Show file tree
Hide file tree
Showing 2 changed files with 13 additions and 6 deletions.
17 changes: 11 additions & 6 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1931,9 +1931,9 @@ def _get_protocol_attrs(cls):
return attrs


def _is_callable_members_only(cls):
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 _get_protocol_attrs(cls))
return all(callable(getattr(cls, attr, None)) for attr in protocol_attrs)


def _no_init_or_replace_init(self, *args, **kwargs):
Expand Down Expand Up @@ -2008,16 +2008,18 @@ def __instancecheck__(cls, instance):
raise TypeError("Instance and class checks can only be used with"
" @runtime_checkable protocols")

protocol_attrs = _get_protocol_attrs(cls)

if ((not getattr(cls, '_is_protocol', False) or
_is_callable_members_only(cls)) and
_is_callable_members_only(cls, protocol_attrs)) and
issubclass(instance.__class__, cls)):
return True
if cls._is_protocol:
if all(hasattr(instance, attr) and
# All *methods* can be blocked by setting them to None.
(not callable(getattr(cls, attr, None)) or
getattr(instance, attr) is not None)
for attr in _get_protocol_attrs(cls)):
for attr in protocol_attrs):
return True
return super().__instancecheck__(instance)

Expand Down Expand Up @@ -2074,7 +2076,10 @@ def _proto_hook(other):
return NotImplemented
raise TypeError("Instance and class checks can only be used with"
" @runtime_checkable protocols")
if not _is_callable_members_only(cls):

protocol_attrs = _get_protocol_attrs(cls)

if not _is_callable_members_only(cls, protocol_attrs):
if _allow_reckless_class_checks():
return NotImplemented
raise TypeError("Protocols with non-method members"
Expand All @@ -2084,7 +2089,7 @@ def _proto_hook(other):
raise TypeError('issubclass() arg 1 must be a class')

# Second, perform the actual structural compatibility check.
for attr in _get_protocol_attrs(cls):
for attr in protocol_attrs:
for base in other.__mro__:
# Check if the members appears in the class dictionary...
if attr in base.__dict__:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Speed up :func:`isinstance` checks against :func:`runtime-checkable
protocols <typing.runtime_checkable>` by up to 6x. Patch by Alex Waygood

0 comments on commit 51ca233

Please sign in to comment.