[spec] Formalize excluded Protocol members
At runtime, the following members are currently excluded by Protocol: (source)
_TYPING_INTERNALS = frozenset({
'__parameters__', '__orig_bases__', '__orig_class__',
'_is_protocol', '_is_runtime_protocol', '__protocol_attrs__',
'__non_callable_proto_members__', '__type_params__',
})
_SPECIAL_NAMES = frozenset({
'__abstractmethods__', '__annotations__', '__dict__', '__doc__',
'__init__', '__module__', '__new__', '__slots__',
'__subclasshook__', '__weakref__', '__class_getitem__',
'__match_args__', '__static_attributes__', '__firstlineno__',
'__annotate__',
})
# These special attributes will be not collected as protocol members.
EXCLUDED_ATTRIBUTES = _TYPING_INTERNALS | _SPECIAL_NAMES | {'_MutableMapping__marker'}
However, neither PEP 544 nor the typing spec formalize this list, which leads to diverging behavior between different type checkers:
Code sample in pyright playground, mypy playground
from typing import TypeIs, Protocol
class MyProtocol(Protocol):
@classmethod
def __subclasshook__(cls, other: type, /) -> TypeIs[type["MyProtocol"]]:
...
x: MyProtocol = int(1) # mypy: ✅ pyright: ❌
Code sample in pyright playground, mypy playground
from typing import Protocol
class MyProtocol(Protocol):
__abstractmethods__: frozenset[str]
x: MyProtocol = int(1) # mypy: ✅ pyright: ❌
A real-world example where this matters is for instance a Protocol for NamedTuple Instances, that checks whether types.get_original_bases(cls) includes typing.NamedTuple inside __subclasshook__.
I think this would be a good addition to the spec. @randolf-scholz, would you be willing to write a proposed modification to the spec and start a discussion thread to solicit comments from the broader community?
I can post something in discourse in the next days.