I hope to have been directed to the right place for my question from https://bugs.python.org/issue43512 I'm completely new to the Python dev process. Therefore, I ask for your indulgence if my approach is not in line with the process.
I would like to collect opinions (especially from authors of PEP 544) if the CPython behavior described below should be regarded as a violation of PEP 544 or at least as a behavior that should be improved.
The section "Subtyping relationships with other types" of PEP 544 states: "A concrete type X is a subtype of protocol P if and only if X implements all protocol members of P with compatible types. In other words, subtyping with respect to a protocol is always structural."
In contrast to that, the behavior of CPython 3.9.2 for protocols with protocol members without default values is as follows: ``` from typing import Protocol
class P(Protocol): protocol_member: str # no default value, but still a protocol member
class X(P): # inherits P but does NOT implement protocol_member, since P did not provide a default value pass
assert isinstance(X(), P) # violates the PEP 544 requirement cited above
X().protocol_member # raises: AttributeError: 'X' object has no attribute 'protocol_member' ```
In this regard, it was argued that "At runtime, protocol classes will be simple ABCs." (PEP 544)
But unfortunately, this is currently not the case. Actually, there is an extra metaclass for protocols, solely to provide an __instancecheck__. https://github.com/python/cpython/blob/3.9/Lib/typing.py#L1096
``` class _ProtocolMeta(ABCMeta): # This metaclass is really unfortunate and exists only because of # the lack of __instancehook__. def __instancecheck__(cls, instance): # We need this method for situations where attributes are # assigned in __init__. if ((not getattr(cls, '_is_protocol', False) or _is_callable_members_only(cls)) and issubclass(instance.__class__, cls)): return True if cls._is_protocol: if all(hasattr(instance, attr) and # All *methods* can be blocked by setting them to None. (not callable(getattr(cls, attr, None)) or getattr(instance, attr) is not None) for attr in _get_protocol_attrs(cls)): return True return super().__instancecheck__(instance) ```
I am inclined to assess the behavior described above as a violation of PEP 544 and an incomplete implementation of `_ProtocolMeta`.