We have a large codebase which uses threads. Many - but not all - of these threads implement a method named 'stop()' which sets a flag, triggers an event, closes a connection, or what-have-you in order to command the thread in question to terminate.
I was writing a thread manager, intended to automatically terminate threads in an organized way at shutdown. It could accept any thread which implemented a 'stop()' method, so how could I type-hint it correctly? 'Aha!' said I, 'This is what those newfangled Protocol things are for! I shall use one of them!' (We only recently updated from 3.7 to 3.11, so quite a lot of features are still 'newfangled' to me.)
However, I then encountered an issue: I could define a Protocol that specified the 'stop()' method easily enough, but if I annotated the manager as taking that, it would accept any class which implemented a method named 'stop()', which was not correct; the manager should only accept threads which implement such a method. I couldn't add 'threading.Thread' as a parent of the protocol; protocols aren't allowed to inherit from normal classes. And there's no syntax for marking an argument as needing to be both a given type and a given protocol.
My proposal is this: Currently, a Protocol is forbidden from inheriting from a normal class, on the basis that it would break transitivity of subtyping.
Instead, allow Protocols to inherit normal classes, with the rule that a class is only considered to implement that protocol if it also inherits the same normal classes. E.g.:
```python
import typing as _tp
class Base:
...
class MyProtocol(Base, _tp.Protocol):
def proto_method(self, string: str) -> bool:
raise NotImplementedError()
class Foo:
def proto_method(self, string: str) -> bool:
...
class Bar(Base):
def proto_method(self, string: str) -> str:
...
class Baz(Base):
def proto_method(self, string: str) -> bool:
...
class Zap(MyProtocol):
def proto_method(self, string: str) -> bool:
...
def my_func(proto: MyProtocol):
...
my_func(Foo()) # Invalid; `Foo` does not inherit `Base` and therefore does not implement `MyProtocol` despite having the necessary method
my_func(Bar()) # Invalid; `Bar` does not implement the method with the correct signature for `MyProtocol`
my_func(Baz()) # Valid; `baz` inherits `Base` explicitly
my_func(Zap()) # Valid; `Zap` inherits `Base` indirectly, via inheriting `MyProtocol` explicitly
```
--
So many books, so little time... - Anon.
You haven't lived
'Till you've heard the floor ring
To the whoop and the call
Of 'Balance and swing!'