Search code examples
pythonpython-typingmypyliskov-substitution-principle

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.


Solution

  • 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.