Reputation: 1378
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
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