Callback protocol with unbound method

stgdsgdddddddddddddddddddd
from typing import Protocol


class MyObj:

    def my_method(self, name: str):
        pass


class Proto(Protocol):

    def __call__(self, obj: MyObj, name: str):
        pass


def my_fn(obj: MyObj, name: str):
    pass


def caller(fn: Proto):
    fn(MyObj(), 'some name')


caller(my_fn)  # passes type check
caller(MyObj.my_method)  # fails type check

I'm using mypy 0.971 for type checking. I have trouble understanding why the second call is illegal according to mypy. Is it in fact incorrect according to Python static typing rules?

Interestingly, if I remove the "name" parameter from all the signatures, the type check passes:

from typing import Protocol


class MyObj:

    def my_method(self):
        pass


class Proto(Protocol):

    def __call__(self, obj: MyObj):
        pass


def my_fn(obj: MyObj):
    pass


def caller(fn: Proto):
    fn(MyObj())


caller(my_fn)  # passes
caller(MyObj.my_method)  # passes

EDIT:

As per @Wombatz explanation, if I modify the protocol to be:

class Proto(Protocol):

    def __call__(self, obj: MyObj, /, name: str):
        pass

it works, since now the name of the first parameter does not matter since it's required to be called with a positional argument.

Wombatz

The problem is that the Protocol is more restrictive than you think.

class Proto(Protocol):
    def __call__(self, obj: MyObj, name: str) -> None:
        pass


def incompatible(what: MyObj, name: str) -> None:
    pass

The function incompatible is also not compatible with the protocol, because the protocol requires a callable where the first argument is a MyObj and its name is obj and the second argument is a str and its name is name.

So in theory, the protocol could be used like this:

def caller(p: Proto) -> None:
    p(obj=MyObj(), name="Hello")

This works for my_func but fails for the method, because the name of the first parameter of the method is self and not obj. So mypy is correct here!

You can define your protocol differently to only require a callable with two positional arguments of type MyObj and str

class Proto(Protocol):
    def __call__(self, obj: MyObj, name: str, /) -> None:
        pass

Now you cannot use named parameters and thus the method and my incompatible function are compatible with the protocol.

Interestingly, if I remove the "name" parameter from all the signatures, the type check passes.

I cannot reproduce that. It should fail and it does

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related