Protocols Subclassing Normal Classes

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

On Fri, 21 Apr 2023 at 22:57, Jordan Macdonald <macdjord@gmail.com> wrote:
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.
To what extent is that actually a problem? Does it need any other features of the thread? My guess is that, after stopping the thread, it may want to join() it; in that case, what you could do is add join to the Protocol. Or whatever else is needed. You're trying to hybridize duck typing and inheritance typing, which seems odd. It should be possible to pick one or the other here. ChrisA

Rather than changing Protocols and affecting lots of users, it seems like was you really want is a generic class that is the "and" to Union's "or"? e.g. def foo(thing: All[Thread, SupportsStop]): ... which seems reasonable. If that appeals to you, then you probably want to raise that on the typing thread?

The formal term for that is intersection types. There is a long-standing thread about that at https://github.com/python/typing/issues/213; there are some uses but the feature would greatly complicate the type system, so it's not clear that it's worth adding. At this point, what Intersection needs is someone to champion the proposal and write a PEP. El lun, 24 abr 2023 a las 6:21, Mathew Elman (<mathew.elman@ocado.com>) escribió:

On Fri, 21 Apr 2023 at 22:57, Jordan Macdonald <macdjord@gmail.com> wrote:
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.
To what extent is that actually a problem? Does it need any other features of the thread? My guess is that, after stopping the thread, it may want to join() it; in that case, what you could do is add join to the Protocol. Or whatever else is needed. You're trying to hybridize duck typing and inheritance typing, which seems odd. It should be possible to pick one or the other here. ChrisA

Rather than changing Protocols and affecting lots of users, it seems like was you really want is a generic class that is the "and" to Union's "or"? e.g. def foo(thing: All[Thread, SupportsStop]): ... which seems reasonable. If that appeals to you, then you probably want to raise that on the typing thread?

The formal term for that is intersection types. There is a long-standing thread about that at https://github.com/python/typing/issues/213; there are some uses but the feature would greatly complicate the type system, so it's not clear that it's worth adding. At this point, what Intersection needs is someone to champion the proposal and write a PEP. El lun, 24 abr 2023 a las 6:21, Mathew Elman (<mathew.elman@ocado.com>) escribió:
participants (4)
-
Chris Angelico
-
Jelle Zijlstra
-
Jordan Macdonald
-
Mathew Elman