5

I'm a little confuse how I'm supposed to type a base class abstract method?

In this case my base class only requires that the inheriting class implements a method named 'learn' that returns None without mandating any arguments.

class MyBaseClass(ABC):
    @abstractmethod
    def learn(self, *args, **kwargs) -> None:
       raise NotImplementedError()

but if I implement it mypy raise en error 'Signature of "learn" incompatible with supertype "MyBaseClass"'

class MyOtherClass(MyBaseClass):
    def learn(self, alpha=0.0, beta=1) -> None:
       # do something
       return None

So how should I declare the learn method in the base class?

MisterMiyagi
  • 44,374
  • 10
  • 104
  • 119
Gavello
  • 1,399
  • 2
  • 13
  • 25
  • Note that ``*args, **kwargs`` doesn't mean "without mandating any arguments" but "accepting all arguments". For example, ``MyBaseClass.learn`` accepts a parameter ``gamma``. ``MyOtherClass`` does not, so it breaks the substitution principle. – MisterMiyagi Nov 15 '21 at 14:37
  • Does this answer your question? [Python 3.6: Signature of {method} incompatible with super type {Class}](https://stackoverflow.com/questions/51003146/python-3-6-signature-of-method-incompatible-with-super-type-class) – MisterMiyagi Nov 15 '21 at 14:42
  • 'joel first solution still has mypy "incompatible with supertype"' I cannot reproduce that. [MyPy accepts the subtyping, it only complains about the missing parameter types](https://mypy-play.net/?mypy=latest&python=3.10&flags=strict&gist=3acc6463fe2a327095e3ae9033268279). – MisterMiyagi Nov 15 '21 at 14:44
  • joel first solution works! – Gavello Nov 15 '21 at 14:45
  • 1
    @MisterMiyagi sure is similar. Might be worth keeping since here we've only got parameters with default values, unlike in that qu. – joel Nov 15 '21 at 14:46
  • @Gavello do you want to mark the solution as accepted if it works? – joel Nov 16 '21 at 11:10

1 Answers1

2

First things first, I'd ask why you want a method without known arguments. That sounds like a design problem.

One solution

It's fine to add new parameters to subclasses if those parameters have default values (and the base class doesn't use **kwargs), like

class MyBaseClass:
    @abstractmethod
    def learn(self) -> None:
       raise NotImplementedError()

class MyOtherClass(MyBaseClass):
    def learn(self, alpha=0.0, beta=1) -> None:
       ...

though you won't be able to specify alpha and beta if you only know it's a MyBaseClass:

def foo(x: MyOtherClass) -> None:
    x.learn(alpha=3)  # ok

def foo(x: MyBaseClass) -> None:
    x.learn(alpha=3)  # not ok

Why didn't *args, **kwargs work?

If you have

class MyBaseClass(ABC):
    @abstractmethod
    def learn(self, *args, **kwargs) -> None:
       raise NotImplementedError()

then consider the function

def foo(x: MyBaseClass) -> None:
    ...

In that function, you can pass anything to x.learn, for example x.learn(1, fish='three'). This has to be true for any x. Since x can be an instance of a subclass of MyBaseClass (such as MyOtherClass), that subclass must also be able to accept those arguments.

joel
  • 6,359
  • 2
  • 30
  • 55
  • I think *args, **kwargs will work but mypy complains – Gavello Nov 15 '21 at 14:27
  • 1
    @Gavello [works fine for me](https://mypy-play.net/?mypy=latest&python=3.10&gist=890574da4f018d73d9395b76867379e1). I'm confident *args, **kwargs won't work for the reasons I say above – joel Nov 15 '21 at 14:42