diff options
author | Alex Waygood <Alex.Waygood@Gmail.com> | 2023-03-31 17:37:24 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-31 17:37:24 (GMT) |
commit | 9048d73f7a5c58be21988250c381f866586687a0 (patch) | |
tree | 34eab4eb226656a666ae7dafbd06ae8c5250da67 /Lib | |
parent | 80163e17d3f826067c5d95198db7696287beb416 (diff) | |
download | cpython-9048d73f7a5c58be21988250c381f866586687a0.zip cpython-9048d73f7a5c58be21988250c381f866586687a0.tar.gz cpython-9048d73f7a5c58be21988250c381f866586687a0.tar.bz2 |
gh-74690: typing: Don't unnecessarily call `_get_protocol_attrs` twice in `_ProtocolMeta.__instancecheck__` (#103141)
Speed up `isinstance()` calls against runtime-checkable protocols
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/typing.py | 31 |
1 files 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__: |