Python: Type Hint for "has method"

Viewed 343

For example, we have a class:

class A:
    def send(msg: bytes) -> None:
        # implementation...
        pass
    
    def recv(n: int) -> bytes:
        # implementation
        pass

and a function:

def a(obj, n: int) -> None:
    received = obj.recv(n)
    obj.send(received)

It's fairly obvious, that not only instances of class A can be passed as the obj argument, but also instances of socket.socket, maybe other classes, which have recv and send implemented.

How can one annotate/type hint obj argument, so that it says something like:

obj type must possess methods send and recv
send method must be of type Callable[[bytes], None]
recv method must be of type Callable[[int], bytes]
1 Answers

What you exactly need is duck-typing (structural subtyping) via typing.Protocol. Some examples are in this list.

Protocol classes are defined like this:

class Proto(Protocol):
    def meth(self) -> int:
        ...

Such classes are primarily used with static type checkers that recognize structural subtyping (static duck-typing), for example:

class C:
    def meth(self) -> int:
        return 0

def func(x: Proto) -> int:
    return x.meth()

func(C())  # Passes static type check

Where a builtin example is

class typing.SupportsIndex

An ABC with one abstract method __index__.

So for your case, it could be something like:

from typing import Protocol


class SupportsSendReceive(Protocol):
    def send(self, msg: bytes) -> None:
        ...
    
    def recv(self, n: int) -> bytes:
        ...


def a(obj: SupportsSendReceive, n: int) -> None:
    received = obj.recv(n)
    obj.send(received)
  • Note that the ellipsis ... doesn't mean you have to substitute code into it. It is really how it should be. Or you could also put pass in there if the 3 dots are bothering :)
Related