]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-74690: typing: Don't unnecessarily call `_get_protocol_attrs` twice in `_ProtocolM...
authorAlex Waygood <Alex.Waygood@Gmail.com>
Fri, 31 Mar 2023 17:37:24 +0000 (18:37 +0100)
committerGitHub <noreply@github.com>
Fri, 31 Mar 2023 17:37:24 +0000 (18:37 +0100)
Speed up `isinstance()` calls against runtime-checkable protocols

Lib/typing.py

index 157a563bbecea8337aad323bb3a9e3663930f6d9..3d086dc1cb90bb6536521b66efdebd1789688a38 100644 (file)
@@ -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__: