Reputation: 5441
I have an Abstract Base Class in python that looks like this:
from abc import abstractmethod
class Task:
@abstractmethod
def run(self, **kwargs):
raise NotImplementedError()
This is provided to the users of my library who will inherit from it with their own options:
class UserTask(Task):
def run(self, price=1, quantity=1, discount=0.5):
return price * quantity * (1 - discount)
In the UserTask
I get the following two pylint warnings:
Number of parameters was 2 in 'Task.run' and is now 4 in overridden 'UserTask.run' method: pylint(arguments-differ)
Variadics removed in overridden 'UserTask.run' method: pylint(arguments-differ)
I have also tried overriding with additional **kwargs
to make sure the signature is fully compliant:
class UserTask(Task):
def run(self, price=1, quantity=1, discount=0.5, **kwargs):
return price * quantity * (1 - discount)
That removes the Variadics removed
warning (which is a legitimate warning) but the Number of parameters
warning remains.
Are these pylint messages overly cautious? It feels like pylint doesn't understand the **
operator... but the warnings will appear in my users' code, not in mine, and I'd rather they don't have to customise their pylint config to use my library!
How should I encourage my users to override the run
method, and/or how should I alter its signature, to enable users to override it without getting this warning?
This is popping up whilst building the Cloud Tasks manager for django-gcp
Upvotes: 2
Views: 2790
Reputation: 530922
The Liskov Substitution Principle requires that an instance of UserTask
be usable anywhere an instance of Task
is expected. Restricting the arguments UserTask.run
can accept violates the LSP.
Make Task
generic in the type of argument it can accept.
from abc import abstractmethod
from typing import Generic, TypeVar
T = TypeVar('T')
class Task(Generic[T]):
@abstractmethod
def run(self, config: T):
raise NotImplementedError()
class UserConfig:
...
class UserTask(Task[UserConfig]):
def run(self, config: UserConfig):
...
Upvotes: 4
Reputation: 1169
You can introduce a Config
class that can be subclassed by your users, and which holds the contents you would normally pass through the **kwargs
:
from abc import abstractmethod
class Config:
...
class Task:
@abstractmethod
def run(self, config: Config) -> None:
...
class UserConfig(Config):
...
class UserTask(Task):
def run(self, config: UserConfig) -> None:
...
Upvotes: 0