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.:

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!'