From 9048d73f7a5c58be21988250c381f866586687a0 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 31 Mar 2023 18:37:24 +0100 Subject: gh-74690: typing: Don't unnecessarily call `_get_protocol_attrs` twice in `_ProtocolMeta.__instancecheck__` (#103141) Speed up `isinstance()` calls against runtime-checkable protocols --- Lib/typing.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index 157a563..3d086dc 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -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): @@ -2000,24 +2000,32 @@ class _ProtocolMeta(ABCMeta): def __instancecheck__(cls, instance): # We need this method for situations where attributes are # assigned in __init__. + is_protocol_cls = getattr(cls, "_is_protocol", False) if ( - getattr(cls, '_is_protocol', False) and + is_protocol_cls and not getattr(cls, '_is_runtime_protocol', False) and not _allow_reckless_class_checks(depth=2) ): raise TypeError("Instance and class checks can only be used with" " @runtime_checkable protocols") - if ((not getattr(cls, '_is_protocol', False) or - _is_callable_members_only(cls)) and - issubclass(instance.__class__, cls)): + if not is_protocol_cls and issubclass(instance.__class__, cls): return True - if cls._is_protocol: + + protocol_attrs = _get_protocol_attrs(cls) + + if ( + _is_callable_members_only(cls, protocol_attrs) + and issubclass(instance.__class__, cls) + ): + return True + + if is_protocol_cls: 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) @@ -2074,7 +2082,10 @@ class Protocol(Generic, metaclass=_ProtocolMeta): 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" @@ -2084,7 +2095,7 @@ class Protocol(Generic, metaclass=_ProtocolMeta): 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__: -- cgit v0.12