Asymmetry w.r.t. issubclass for protocols with (non-)method members and classes that have been registered via Protocol.register
PEP 544 introduces `typing.Protocol` for declaring interfaces that work with structural subtyping. Nevertheless since `abc.ABCMeta` is in the MRO of `type(Protocol)` we can still use the plain old `Protocol.register` for registering subtypes. This seems to override the "normal" instance and subclass checks performed by `Protocol` since we can register any non-conforming class. For example: from typing_extensions import Protocol, runtime_checkable @runtime_checkable class Foo(Protocol): def foo(self) -> int: raise NotImplementedError @Foo.register class Bar: # This class does not conform to the 'Foo' interface but we register it nevertheless. def bar(self) -> int: return 1 print(isinstance(Bar(), Foo)) # True print(issubclass(Bar, Foo)) # True Here both checks, instance and subclass, pass which suggests that the default behavior has been overridden by `@Foo.register`. However when using a `Protocol` with non-method members the [sanity checks](https://github.com/python/cpython/blob/aa909b6b1242b4969b20bb0250ac386f9b412...) still seem to take precedence: @runtime_checkable class Foo(Protocol): foo : int @Foo.register class Bar: # This class does not conform to the 'Foo' interface but we register it nevertheless. bar = 1 print(isinstance(Bar(), Foo)) # True print(issubclass(Bar, Foo)) # TypeError: Protocols with non-method members don't support issubclass() This produces a `TypeError: Protocols with non-method members don't support issubclass()` for the subclass check. Since for the methods-only-members case (and for the instance checks) `register` allows us to register subtypes completely at will it feels strange that for the non-methods-members case `Protocol` claims to know better and intercepts this attempt. Am I mistaken here or should the subclass check prefer the `ABCMeta` behavior over the custom checks implemented by `Protocol`? The current behavior seems asymmetric for methods-only and non-methods members protocols.
This is caused by the fact that .register() has lower priority than
__sunclasskook__(), i.e. the _abc_registry is checked after the subclass
hook.
So registering a class is a fallback, not an override. This is the same for
normal ABCs as well, not just protocols.
(Note: the consistency/safety checks for protocols are done in
__subclasshook__())
There are three possible ways forward:
* Keep the status quo
* Manually check registry in __subclasshook__() for protocols, thus making
.register() behave like override
* Remove the safety checks, and rely solely on static type checkers
The best way forward depends on how problematic is current behaviour for
you. In any case I would propose to just open an issue on bugs.python.org
and continue discussion there.
--
Ivan
On Mon, 14 Oct 2019 at 20:05, Dominik Vilsmeier
PEP 544 introduces `typing.Protocol` for declaring interfaces that work with structural subtyping. Nevertheless since `abc.ABCMeta` is in the MRO of `type(Protocol)` we can still use the plain old `Protocol.register` for registering subtypes. This seems to override the "normal" instance and subclass checks performed by `Protocol` since we can register any non-conforming class. For example:
from typing_extensions import Protocol, runtime_checkable
@runtime_checkable class Foo(Protocol): def foo(self) -> int: raise NotImplementedError
@Foo.register class Bar: # This class does not conform to the 'Foo' interface but we register it nevertheless. def bar(self) -> int: return 1
print(isinstance(Bar(), Foo)) # True print(issubclass(Bar, Foo)) # True
Here both checks, instance and subclass, pass which suggests that the default behavior has been overridden by `@Foo.register`.
However when using a `Protocol` with non-method members the [sanity checks]( https://github.com/python/cpython/blob/aa909b6b1242b4969b20bb0250ac386f9b412...) still seem to take precedence:
@runtime_checkable class Foo(Protocol): foo : int
@Foo.register class Bar: # This class does not conform to the 'Foo' interface but we register it nevertheless. bar = 1
print(isinstance(Bar(), Foo)) # True print(issubclass(Bar, Foo)) # TypeError: Protocols with non-method members don't support issubclass()
This produces a `TypeError: Protocols with non-method members don't support issubclass()` for the subclass check.
Since for the methods-only-members case (and for the instance checks) `register` allows us to register subtypes completely at will it feels strange that for the non-methods-members case `Protocol` claims to know better and intercepts this attempt.
Am I mistaken here or should the subclass check prefer the `ABCMeta` behavior over the custom checks implemented by `Protocol`? The current behavior seems asymmetric for methods-only and non-methods members protocols. _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/
participants (2)
-
Dominik Vilsmeier
-
Ivan Levkivskyi