Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1782,6 +1782,14 @@ def _pickle_pskwargs(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):
# This metaclass is somewhat unfortunate,
# but is necessary for several reasons...
Expand Down Expand Up @@ -1841,7 +1849,7 @@ def __subclasscheck__(cls, other):
"Instance and class checks can only be used with "
"@runtime_checkable protocols"
)
return super().__subclasscheck__(other)
return _abc_subclasscheck(cls, other)

def __instancecheck__(cls, instance):
# We need this method for situations where attributes are
Expand All @@ -1850,7 +1858,7 @@ def __instancecheck__(cls, instance):
return type.__instancecheck__(cls, instance)
if not getattr(cls, "_is_protocol", False):
# i.e., it's a concrete subclass of a protocol
return super().__instancecheck__(instance)
return _abc_instancecheck(cls, instance)

if (
not getattr(cls, '_is_runtime_protocol', False) and
Expand All @@ -1859,7 +1867,7 @@ def __instancecheck__(cls, instance):
raise TypeError("Instance and class checks can only be used with"
" @runtime_checkable protocols")

if super().__instancecheck__(instance):
if _abc_instancecheck(cls, instance):
return True

getattr_static = _lazy_load_getattr_static()
Expand Down
Original file line number Diff line number Diff line change
@@ -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.