medley56
medley56

Reputation: 1378

Signature mismatch in derived class that is more explicit than the parent class

I have an abstract parent class like:

class Parent(ABC):
    @classmethod
    @abstractmethod
    def method(cls, *args, basepath: str = None):
        raise NotImplementedError()

and a concrete child class like:

class Child(Parent):
    @classmethod
    def method(cls, arg1: int, arg2: str, basepath: str = None):
        do_stuff_with_args(arg1, arg2)
        do_stuff_with_basepath(basepath)

My IDE (PyCharm) is telling me that the method signature of Child.method doesn't match Parent.method. This seems like an incorrect inspection because the child implementation is Liskov substitutable for the parent implementation. i.e. the specifically args to the child still satisfy the *args requirement in the parent.

The reason for this implementation is so different children can have different specific args but we enforce inclusion of basepath in every child implementation.

Obviously this isn't a problem for the execution of the code and in fact Liskov is sort of a non-issue since the parent is an ABC anyway. I'm trying to understand if there's something I'm missing or if PyCharm is just being dumb.

Is PyCharm wrong or am I wrong? Why?

Upvotes: 1

Views: 770

Answers (1)

jsbueno
jsbueno

Reputation: 110516

These theoretical questions on inheritance usually are more though to think about and get a "correct" answer than what it is in practice to work with OOP and inheritance.

That said, if you can just ignore PyCharm's warning on this issue,just do it.

I myself even dislike the strict enforcing of the LSP itself - as when you come to "real world" cases, you will often find instances where you need the parent class to be usable were the child is, and not the converse. Or other times, you just want to group functionalities and have good code reuse, regardless if any member in the inheritance hierarchy is a substitute for others.

In Python, with it is flexibility for parameters, arguments, attributes and all the other allowed dynamic things, this becomes even more apparent.

Still, there is one thing you might try doing there to check if PyCharm would stop complaining - or at least, it could make you less error-prone: that is enforcing that basepath is a named only parameter in the subclasses:

    def method(cls, arg1: int, arg2: str, *, basepath: str = None):
        do_stuff_with_args(arg1, arg2)
        do_stuff_with_basepath(basepath)

The extra "*" ensures basepath cannot be passed as a positional argument, like it can't in the base class due to *args being there. That is one difference that makes I myself say these two signatures are not compatible.

Upvotes: 1

Related Questions