-
-
Notifications
You must be signed in to change notification settings - Fork 34.2k
Open
Labels
stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytopic-typingtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Description
Bug report
Bug description:
If a protocol is named Protocol or Generic then it will ignore any defined members.
when you get lazy with naming in your unit tests this makes for quite a head scratcher...
Minimal Reproduction
import typing
class Protocol(typing.Protocol):
a: int
members = typing.get_protocol_members(Protocol)
assert members == {"a"}, f"Expected members to be {{'a'}}, got {members}"Output:
Traceback (most recent call last):
File ".../test_protocol.py", line 10, in <module>
assert members == {"a"}, f"Expected members to be {{'a'}}, got {members}"
^^^^^^^^^^^^^^^^
AssertionError: Expected members to be {'a'}, got frozenset()
Expected Output:
Nothing, script should succeed without error.
Investigation
The problem appears to stem from the _get_protocol_attrs function used by _ProtocolMeta which is defined as follows:
Lines 1879 to 1899 in 171133a
| def _get_protocol_attrs(cls): | |
| """Collect protocol members from a protocol class objects. | |
| This includes names actually defined in the class dictionary, as well | |
| as names that appear in annotations. Special names (above) are skipped. | |
| """ | |
| attrs = set() | |
| for base in cls.__mro__[:-1]: # without object | |
| if base.__name__ in {'Protocol', 'Generic'}: | |
| continue | |
| try: | |
| annotations = base.__annotations__ | |
| except Exception: | |
| # Only go through annotationlib to handle deferred annotations if we need to | |
| annotations = _lazy_annotationlib.get_annotations( | |
| base, format=_lazy_annotationlib.Format.FORWARDREF | |
| ) | |
| for attr in (*base.__dict__, *annotations): | |
| if not attr.startswith('_abc_') and attr not in EXCLUDED_ATTRIBUTES: | |
| attrs.add(attr) | |
| return attrs |
The if statement on line 1887 is too loose in what it matches.
Possibile Fix
The check for base needs to be more specific, either using the instance itself:
if base in {Protocol, Generic}:
continueor checking the module too, in order to ensure its the correct class being excluded:
if base.__name__ in {'Protocol', 'Generic'} and base.__module__ == 'typing':
continueCPython versions tested on:
3.14
Operating systems tested on:
macOS
Linked PRs
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytopic-typingtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error