coady / multimethod

Multiple argument dispatching.
https://coady.github.io/multimethod
Other
277 stars 24 forks source link

@multimethod in combination with Protocol = Error #58

Closed Valentin-Miroshnichenko closed 2 years ago

Valentin-Miroshnichenko commented 2 years ago

I'm trying to use @multimethod in combination with Protocol. But at runtime I get this error

TypeError: Protocols with non-method members don't support issubclass()

@runtime_checkable
class SenderContext(Protocol):
    @property
    def cancellation_token(self) -> bool:
        ...

    def send(self, target: PID, message: Any) -> None:
        raise NotImplementedError("Should Implement this method")

@multimethod
def subscribe(self, msg_type: type,
              context: SenderContext) -> None:

How can I fix this error? If I remove the property cancellation_token, then everything works fine. But I need a property cancellation_token.

coady commented 2 years ago

I don't think there's a general solution, because multimethod relies on issubclass and Protocol explicitly forbids using issubclass with data protocols.

One workaround would be to use overload, which supports isinstance.

In []: @runtime_checkable
    ...: class Closable(Protocol):
    ...:     def close(self): ...
    ...:     @property
    ...:     def name(self): ...
    ...: 

In []: isinstance(f, Closable)
Out[]: True

In []: issubclass(type(f), Closable)
TypeError: Protocols with non-method members don't support issubclass()

In []: @overload
    ...: def func(arg: Closable): ...

In []: func(f)
mofeing commented 2 years ago

@Valentin-Miroshnichenko Just for completeness, I solved this by using formal interfaces with abc.ABCMeta as metaclass. Instead of writing your Closable as a Protocol, you could instead write it like this:

import abc

class Closable(metaclass=abc.ABCMeta):
    @classmethod
    def __subclasshook__(cls, subcls):
        return hasattr(subcls, "close") and callable(subcls.close) \
            and hasattr(subcls, "name") and type(subcls.name) == property

It's a lil cumbersome, but it gets the work done. If you have many Protocols, you surely can code a decorator that transforms them to interfaces of this kind. Furthermore, you can explicitly register a class using Closable.register(MyClass).