From 51ca23338bf57c4a1945ca4604cc2c6582904249 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Thu, 30 Mar 2023 23:52:17 +0100 Subject: [PATCH 1/3] gh-74690: typing: Don't unnecessarily call `_get_protocol_attrs` twice in `_ProtocolMeta.__instancecheck__` --- Lib/typing.py | 17 +++++++++++------ ...023-03-30-23-51-22.gh-issue-74690.p2c4Co.rst | 2 ++ 2 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-03-30-23-51-22.gh-issue-74690.p2c4Co.rst diff --git a/Lib/typing.py b/Lib/typing.py index 157a563bbecea8..df11b779e4fd33 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): @@ -2008,8 +2008,10 @@ 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: @@ -2017,7 +2019,7 @@ def __instancecheck__(cls, instance): # 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 +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" @@ -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__: diff --git a/Misc/NEWS.d/next/Library/2023-03-30-23-51-22.gh-issue-74690.p2c4Co.rst b/Misc/NEWS.d/next/Library/2023-03-30-23-51-22.gh-issue-74690.p2c4Co.rst new file mode 100644 index 00000000000000..0fc6c00be53077 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-03-30-23-51-22.gh-issue-74690.p2c4Co.rst @@ -0,0 +1,2 @@ +Speed up :func:`isinstance` checks against :func:`runtime-checkable +protocols ` by up to 6x. Patch by Alex Waygood From 285a41b656659a4e857af1a78960a1d4fc89a076 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 31 Mar 2023 10:30:05 +0100 Subject: [PATCH 2/3] Address review --- Lib/typing.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index df11b779e4fd33..3d086dc1cb90bb 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2000,21 +2000,27 @@ 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 is_protocol_cls and issubclass(instance.__class__, cls): + return True + protocol_attrs = _get_protocol_attrs(cls) - if ((not getattr(cls, '_is_protocol', False) or - _is_callable_members_only(cls, protocol_attrs)) and - issubclass(instance.__class__, cls)): + if ( + _is_callable_members_only(cls, protocol_attrs) + and issubclass(instance.__class__, cls) + ): return True - if cls._is_protocol: + + 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 From b6d1c9507988e2670f3240fe44256d0ac731eada Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 31 Mar 2023 18:06:54 +0100 Subject: [PATCH 3/3] Delete 2023-03-30-23-51-22.gh-issue-74690.p2c4Co.rst --- .../next/Library/2023-03-30-23-51-22.gh-issue-74690.p2c4Co.rst | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 Misc/NEWS.d/next/Library/2023-03-30-23-51-22.gh-issue-74690.p2c4Co.rst diff --git a/Misc/NEWS.d/next/Library/2023-03-30-23-51-22.gh-issue-74690.p2c4Co.rst b/Misc/NEWS.d/next/Library/2023-03-30-23-51-22.gh-issue-74690.p2c4Co.rst deleted file mode 100644 index 0fc6c00be53077..00000000000000 --- a/Misc/NEWS.d/next/Library/2023-03-30-23-51-22.gh-issue-74690.p2c4Co.rst +++ /dev/null @@ -1,2 +0,0 @@ -Speed up :func:`isinstance` checks against :func:`runtime-checkable -protocols ` by up to 6x. Patch by Alex Waygood