summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorAlex Waygood <Alex.Waygood@Gmail.com>2023-03-31 17:37:24 (GMT)
committerGitHub <noreply@github.com>2023-03-31 17:37:24 (GMT)
commit9048d73f7a5c58be21988250c381f866586687a0 (patch)
tree34eab4eb226656a666ae7dafbd06ae8c5250da67 /Lib
parent80163e17d3f826067c5d95198db7696287beb416 (diff)
downloadcpython-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.py31
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__: