thclark
thclark

Reputation: 5441

How to define keyword/variadic arguments in a NotImplementedYet ABC method (avoiding pylint: arguments-differ)

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:

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.

The question

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?

Background for interest

This is popping up whilst building the Cloud Tasks manager for django-gcp

Upvotes: 2

Views: 2790

Answers (2)

chepner
chepner

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

dudenr33
dudenr33

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

Related Questions