[Python-checkins] gh-74690: typing: Don't unnecessarily call `_get_protocol_attrs` twice in `_ProtocolMeta.__instancecheck__` (#103141)

AlexWaygood webhook-mailer at python.org
Fri Mar 31 13:37:32 EDT 2023


https://github.com/python/cpython/commit/9048d73f7a5c58be21988250c381f866586687a0
commit: 9048d73f7a5c58be21988250c381f866586687a0
branch: main
author: Alex Waygood <Alex.Waygood at Gmail.com>
committer: AlexWaygood <Alex.Waygood at Gmail.com>
date: 2023-03-31T18:37:24+01:00
summary:

gh-74690: typing: Don't unnecessarily call `_get_protocol_attrs` twice in `_ProtocolMeta.__instancecheck__` (#103141)

Speed up `isinstance()` calls against runtime-checkable protocols

files:
M Lib/typing.py

diff --git a/Lib/typing.py b/Lib/typing.py
index 157a563bbece..3d086dc1cb90 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 @@ 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 +2095,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__:



More information about the Python-checkins mailing list