gh-156413: Let a None-valued non-callable member keep the Protocol fast path - #156451
Open
adamtheturtle wants to merge 2 commits into
Open
gh-156413: Let a None-valued non-callable member keep the Protocol fast path#156451adamtheturtle wants to merge 2 commits into
None-valued non-callable member keep the Protocol fast path#156451adamtheturtle wants to merge 2 commits into
Conversation
…col fast path `_ProtocolMeta.__instancecheck__` treats a member set to `None` as "explicitly not implemented" only for callable members, but `_proto_hook` treated any `None` in a class `__dict__` that way. A class that set a non-callable protocol member to `None` therefore passed `isinstance()` but was rejected by the subclass hook, so it never entered `ABCMeta`'s cache and re-walked every protocol member on every call. Give `_proto_hook` the same rule, so such a class is cached like any other. `None`-valued *method* members are still rejected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016TM2nkuPFj6FFmZyUJZYUQ
adamtheturtle
requested review from
AlexWaygood and
JelleZijlstra
as code owners
August 27, 2026 09:47
picnixz
reviewed
Aug 27, 2026
| # all of the protocol members again. | ||
| @runtime_checkable | ||
| class P(Protocol): | ||
| x = 1 |
Member
There was a problem hiding this comment.
Can you check with a protocol defining a property as well? it's not really a callable strictly speaking.
Contributor
Author
There was a problem hiding this comment.
(Deleted comment that the bot wrote).
Yes - added in a new commit.
A property is not callable when looked up on the class, so it lands in __non_callable_proto_members__ alongside a plain class attribute. Both subtests fail without the change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016TM2nkuPFj6FFmZyUJZYUQ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
_ProtocolMeta.__instancecheck__treats a member set toNoneas "explicitly not implemented" only for callable members:_proto_hookapplies the same sentinel to every member, callable or not. So a class that sets a non-callable protocol member toNonepassesisinstance()but is rejected by the subclass hook. It lands inABCMeta's negative cache instead of the positive one, and every subsequentisinstance()call falls through to thegetattr_staticloop over all protocol members.This makes the check O(N) in the number of protocol members, on every call, for a class that conforms.
Performance
Release build of this branch, macOS on Apple silicon, best of 11 runs of 20,000
isinstance()calls, ns/call.Phas N property members;Baddiffers fromGoodonly in setting the last one toNone.GoodBad, beforeBad, afterThe before column scales at roughly 500 ns per protocol member. After the change
Badis a cache hit likeGood, andGooditself is unaffected.This gives
_proto_hookthe same rule, so such a class is cached like any other.None-valued method members are still rejected, and a missing attribute still fails.The only externally visible change is via
_allow_reckless_class_checks: anissubclass()originating inabc,functoolsor_py_abcnow returnsTruewhere it returnedFalse, which is whatisinstance()already answers for the same pair. A directissubclass()still raisesTypeErrorfor protocols with non-method members, so user-facing behaviour is unchanged.typing: oneNone-valued member makesisinstance()on aruntime_checkableProtocol O(N) per call #156413