GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-156413: Let a `None`-valued non-callable member keep the Protocol fast path by adamtheturtle · Pull Request #156451 · python/cpython

Conversation

@adamtheturtle

@adamtheturtle adamtheturtle commented Aug 27, 2026 •
edited by bedevere-app Bot
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

_ProtocolMeta.__instancecheck__ treats a member set to None as "explicitly not implemented" only for callable members:

if val is None and attr not in cls.__non_callable_proto_members__:
    break

_proto_hook applies the same sentinel to every member, callable or not. So a class that sets a non-callable protocol member to None passes isinstance() but is rejected by the subclass hook. It lands in ABCMeta's negative cache instead of the positive one, and every subsequent isinstance() call falls through to the getattr_static loop over all protocol members.

>>> [r() for r in _abc._get_dump(P)[1]], [r() for r in _abc._get_dump(P)[2]]
([<class 'Good'>], [<class 'Bad'>])   # positive, negative

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. P has N property members; Bad differs from Good only in setting the last one to None.

N Good Bad, before Bad, after
2 178 1334 (7.5x) 162 (1.0x)
5 166 2861 (17x) 170 (1.0x)
20 167 10704 (64x) 169 (1.0x)
50 183 26882 (147x) 172 (1.0x)
110 192 55820 (291x) 186 (1.0x)

The before column scales at roughly 500 ns per protocol member. After the change Bad is a cache hit like Good, and Good itself is unaffected.

This gives _proto_hook the 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: an issubclass() originating in abc, functools or _py_abc now returns True where it returned False, which is what isinstance() already answers for the same pair. A direct issubclass() still raises TypeError for protocols with non-method members, so user-facing behaviour is unchanged.

  • Issue: typing: one None-valued member makes isinstance() on a runtime_checkable Protocol O(N) per call #156413

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@adamtheturtle @claude
pythongh-156413: Let a None-valued non-callable member keep the Proto…
307fe8f
…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) <[email protected]>
Claude-Session: https://claude.ai/code/session_016TM2nkuPFj6FFmZyUJZYUQ
@adamtheturtle
adamtheturtle requested review from AlexWaygood and JelleZijlstra as code owners August 27, 2026 09:47
@bedevere-app bedevere-app Bot mentioned this pull request Aug 27, 2026
typing: one None-valued member makes isinstance() on a runtime_checkable Protocol O(N) per call #156413
Open
@bedevere-app bedevere-app Bot added the awaiting review label Aug 27, 2026
picnixz
picnixz reviewed Aug 27, 2026
View reviewed changes
Comment thread Lib/test/test_typing.py
# all of the protocol members again.
@runtime_checkable
class P(Protocol):
x = 1

@picnixz picnixz Aug 27, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check with a protocol defining a property as well? it's not really a callable strictly speaking.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@adamtheturtle adamtheturtle Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Deleted comment that the bot wrote).

Yes - added in a new commit.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@adamtheturtle @claude
Cover a property member in the regression test
7f7bc3f
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) <[email protected]>
Claude-Session: https://claude.ai/code/session_016TM2nkuPFj6FFmZyUJZYUQ
JelleZijlstra
JelleZijlstra approved these changes Aug 29, 2026
View reviewed changes
@bedevere-app bedevere-app Bot added awaiting merge and removed awaiting review labels Aug 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@picnixz picnixz picnixz left review comments
@JelleZijlstra JelleZijlstra JelleZijlstra approved these changes
@AlexWaygood AlexWaygood Awaiting requested review from AlexWaygood AlexWaygood is a code owner

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

awaiting merge

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

3 participants

@adamtheturtle @JelleZijlstra @picnixz
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.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.