X Tutup
Skip to content

gh-145688: Fix _get_protocol_attrs matching user classes named "Protocol" or "Generic"#145724

Open
mvanhorn wants to merge 3 commits intopython:mainfrom
mvanhorn:osc/145688-fix-protocol-name-check
Open

gh-145688: Fix _get_protocol_attrs matching user classes named "Protocol" or "Generic"#145724
mvanhorn wants to merge 3 commits intopython:mainfrom
mvanhorn:osc/145688-fix-protocol-name-check

Conversation

@mvanhorn
Copy link
Contributor

@mvanhorn mvanhorn commented Mar 10, 2026

Fixes #145688

Summary

_get_protocol_attrs() used base.__name__ in {'Protocol', 'Generic'} to skip the base typing classes when collecting protocol members. This also matched user-defined Protocol subclasses that happened to share those names, causing get_protocol_members() and __protocol_attrs__ to return empty results.

Fix: Changed the check to also verify base.__module__ == 'typing', so only the actual typing.Protocol and typing.Generic are skipped.

Note: A pure identity check (base is Protocol) was considered but doesn't work because _get_protocol_attrs is called during module initialization before Protocol is defined.

Test plan

  • Added test_get_protocol_members_named_protocol_or_generic covering both "Protocol" and "Generic" named subclasses
  • All 709 test_typing tests pass
  • NEWS entry added

This contribution was developed with AI assistance (Claude Code).

…"Protocol" or "Generic"

The `_get_protocol_attrs` function used `base.__name__ in {'Protocol', 'Generic'}`
to skip the base typing classes, which also skipped user-defined Protocol
subclasses that happened to be named "Protocol" or "Generic". This caused
`get_protocol_members()` and `__protocol_attrs__` to return empty results
for such classes.

Changed to check both `__name__` and `__module__` to ensure only the actual
`typing.Protocol` and `typing.Generic` classes are skipped.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Lib/typing.py Outdated
attrs = set()
for base in cls.__mro__[:-1]: # without object
if base.__name__ in {'Protocol', 'Generic'}:
if base.__name__ in {'Protocol', 'Generic'} and base.__module__ == 'typing':
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure if this is necessary, but maybe raise a ValueError if this is true?
If you end up raising an exception, just remember to update the test and NEWS entry.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion! I think skipping is the right behavior here though - a user class legitimately named Protocol should just have its attrs collected normally, not raise an error.

@@ -0,0 +1,5 @@
Fixed :func:`typing.get_protocol_members` and :attr:`~typing.Protocol.__protocol_attrs__`
returning empty results for Protocol subclasses named ``"Protocol"`` or ``"Generic"``.
The check in :func:`_get_protocol_attrs` now uses identity comparison instead of
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think that you need to explain how you fixed it. Maybe something like this:
Now a :class:typing.Protocol class can be named Protocol or Generic

This needs more refining, but I think it could lead to a better description.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point - simplified to focus on the user-visible behavior. Thanks!

@AlexWaygood
Copy link
Member

If I remember correctly, changing this might break the typing_extensions backport of Protocol (which backports a number of bugfixes and optimisations to older Python versions). It's unfortunately important that _ProtocolMeta understands typing_extensions.Protocol as being the same as typing.Protocol.

…ttrs

Address review feedback: also skip bases from typing_extensions module
so the backported Protocol class is treated the same as typing.Protocol.
Simplify NEWS entry to focus on user-visible behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mvanhorn
Copy link
Contributor Author

Good catch, thanks @AlexWaygood! Updated the check to base.__module__ in {'typing', 'typing_extensions'} so it handles the backported Protocol as well.

@AlexWaygood
Copy link
Member

AlexWaygood commented Mar 10, 2026

Good catch, thanks @AlexWaygood! Updated the check to base.__module__ in {'typing', 'typing_extensions'} so it handles the backported Protocol as well.

hmm, but there may be other third-party libraries as well that have come to rely on the current behaviour. For example, there is also beartype.Protocol, that also uses (a subclass of) _ProtocolMeta. Will this change break them? (That's an honest question: I don't know, and there may be other libraries that are doing similar things as well that we don't know about.)

Per @AlexWaygood's review, checking base.__module__ in {'typing',
'typing_extensions'} is fragile and would break third-party libraries
like beartype that define their own Protocol using _ProtocolMeta.

Use `base is Protocol or base is Generic` instead, which is the same
pattern already used at line 1176 in _generic_class_getitem. This
precisely targets the two base classes we want to skip without
affecting any third-party Protocol implementations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@python-cla-bot
Copy link

python-cla-bot bot commented Mar 10, 2026

All commit authors signed the Contributor License Agreement.

CLA signed

@mvanhorn
Copy link
Contributor Author

Great point, @AlexWaygood - the module allowlist approach is fragile. Switched to identity checks (base is Protocol or base is Generic) in the latest push. This is the same pattern already used at line 1176 in _generic_class_getitem, and it precisely targets the two base classes without affecting third-party implementations like beartype.Protocol.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Protocol named "Protocol" or "Generic" has no members.

3 participants

X Tutup