gh-74690: Optimise isinstance() and issubclass() calls against runtime-checkable protocols by avoiding costly super() calls (#112708)

This commit is contained in:
Alex Waygood 2023-12-04 15:41:41 +00:00 committed by GitHub
parent 9560e0d6d7
commit c718ab92a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 16 additions and 3 deletions

View file

@ -1782,6 +1782,14 @@ copyreg.pickle(ParamSpecKwargs, _pickle_pskwargs)
del _pickle_psargs, _pickle_pskwargs del _pickle_psargs, _pickle_pskwargs
# Preload these once, as globals, as a micro-optimisation.
# This makes a significant difference to the time it takes
# to do `isinstance()`/`issubclass()` checks
# against runtime-checkable protocols with only one callable member.
_abc_instancecheck = ABCMeta.__instancecheck__
_abc_subclasscheck = ABCMeta.__subclasscheck__
class _ProtocolMeta(ABCMeta): class _ProtocolMeta(ABCMeta):
# This metaclass is somewhat unfortunate, # This metaclass is somewhat unfortunate,
# but is necessary for several reasons... # but is necessary for several reasons...
@ -1841,7 +1849,7 @@ class _ProtocolMeta(ABCMeta):
"Instance and class checks can only be used with " "Instance and class checks can only be used with "
"@runtime_checkable protocols" "@runtime_checkable protocols"
) )
return super().__subclasscheck__(other) return _abc_subclasscheck(cls, other)
def __instancecheck__(cls, instance): def __instancecheck__(cls, instance):
# We need this method for situations where attributes are # We need this method for situations where attributes are
@ -1850,7 +1858,7 @@ class _ProtocolMeta(ABCMeta):
return type.__instancecheck__(cls, instance) return type.__instancecheck__(cls, instance)
if not getattr(cls, "_is_protocol", False): if not getattr(cls, "_is_protocol", False):
# i.e., it's a concrete subclass of a protocol # i.e., it's a concrete subclass of a protocol
return super().__instancecheck__(instance) return _abc_instancecheck(cls, instance)
if ( if (
not getattr(cls, '_is_runtime_protocol', False) and not getattr(cls, '_is_runtime_protocol', False) and
@ -1859,7 +1867,7 @@ class _ProtocolMeta(ABCMeta):
raise TypeError("Instance and class checks can only be used with" raise TypeError("Instance and class checks can only be used with"
" @runtime_checkable protocols") " @runtime_checkable protocols")
if super().__instancecheck__(instance): if _abc_instancecheck(cls, instance):
return True return True
getattr_static = _lazy_load_getattr_static() getattr_static = _lazy_load_getattr_static()

View file

@ -0,0 +1,5 @@
Speedup :func:`isinstance` checks by roughly 20% for
:func:`runtime-checkable protocols <typing.runtime_checkable>`
that only have one callable member.
Speedup :func:`issubclass` checks for these protocols by roughly 10%.
Patch by Alex Waygood.