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!'
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/7YCLH2CUZAZEXSZXUKP2LQLG7P4RHMIV/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to