简体   繁体   中英

mypy seems to think that (*args, **kwargs) could match to any funtion signature?

How does mypy apply the Liskov substitution principle to *args, **kwargs parameters?

I thought the following code should fail a mypy check since some calls to f allowed by the Base class are not allowed by C , but it actually passed. Are there any reasons for this?

from abc import ABC, abstractmethod
from typing import Any


class Base(ABC):
    @abstractmethod
    def f(self, *args: Any, **kwargs: Any) -> int:
        pass


class C(Base):
    def f(self, batch: int, train: bool) -> int:
        return 1

I also tried to remove either *args or **kwargs , both failed.

Unlike Daniil said in currently accepted answer, the reason is exactly (*args: Any, **kwargs: Any) signature part.

Please check the corresponding discussion on mypy issue tracker :

I actually like this idea, I have seen this confusion several times, and although it is a bit unsafe, most of the time when people write (*args, **kwargs) it means "don't care", rather than "should work for all calls".

[GVR] Agreed, this is a case where practicality beats purity.

So, mypy gives a special treatment to functions of form

# _T is arbitrary type
class _:
    def _(self, *args, **kwargs) -> _T: ...

and considers them fully equivalent to Callable[..., _T] .

Yes, this actually violates LSP, of course, but this was designed specially to allow declaring functions with signature "just ignore my parameters".

To declare the broadest possible function that really accepts arbitrary positional and keyword arguments, you should use object in signature instead.

This has nothing to do with *args or **kwargs per se. The reason for this is strictly the fact that you used typing.Any for both annotations.

The Any annotation is basically a Jedi mind trick for the type checker to the effect of:

These are the types you were looking for.

No matter what, it will always pass.

For this reason, the typing documentation specifically recommends to use object as much as possible instead of Any , when you mean to say something like "the broadest possible type" . Any should be reserved as the last resort, when you bump against the limits of the Python typing system.

The mypy docs also have a section explaining the difference between Any and object .

If you change even one of those Any annotations to object , you will be rightfully chastised by mypy with an [override] error for C.f .

Example:

from typing import Any

class Base:
    def f(self, *args: object, **kwargs: Any) -> int:
        return 2

class C(Base):
    def f(self, batch: int, train: bool) -> int:  # now this is an error
        return 1

Whereas the combination of saying "any number of positional and keyword-arguments" together with "each argument will always pass the type check" essentially translates to "no override will ever be wrong" (in terms of arguments).

So I would suggest using object instead of Any everywhere , unless you cannot avoid using the latter.

These confusions are one of the reasons I think the choice to name this construct Any is so unfortunate.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM