Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork34.2k
Open
Description
Bug report
Bug description:
If a protocol is namedProtocol orGeneric 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
importtypingclassProtocol(typing.Protocol):a:intmembers=typing.get_protocol_members(Protocol)assertmembers== {"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 in171133a
| 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() | |
| forbaseincls.__mro__[:-1]:# without object | |
| ifbase.__name__in {'Protocol','Generic'}: | |
| continue | |
| try: | |
| annotations=base.__annotations__ | |
| exceptException: | |
| # Only go through annotationlib to handle deferred annotations if we need to | |
| annotations=_lazy_annotationlib.get_annotations( | |
| base,format=_lazy_annotationlib.Format.FORWARDREF | |
| ) | |
| forattrin (*base.__dict__,*annotations): | |
| ifnotattr.startswith('_abc_')andattrnotinEXCLUDED_ATTRIBUTES: | |
| attrs.add(attr) | |
| returnattrs |
The if statement on line 1887 is too loose in what it matches.
Possibile Fix
The check forbase needs to be more specific, either using the instance itself:
ifbasein {Protocol,Generic}:continue
or checking the module too, in order to ensure its the correct class being excluded:
ifbase.__name__in {'Protocol','Generic'}andbase.__module__=='typing':continue
CPython versions tested on:
3.14
Operating systems tested on:
macOS