Is it possible to have an abstract class require some specific arguments for a method of a Python class, while still leaving the possibility for concrete classes to add more arguments?
from abc import ABC, abstractmethod
class FooBase(ABC):
@abstractmethod
def __init__(self, required: str, also_required: int):
pass
And then the concrete class would go, conceptually:
class Foo(FooBase):
def __init__(self, required: str, also_required: int, something_else: float):
do_stuff()
Context
This is in the context of a package/library intended to be imported by client. I'd like to provide the FooBase
abstract class, that has a particular contract regarding other parts of the library. Clients would be free to implement their concrete Foo
, but in this use case, the method arguments of __init__
are to be considered as "mandatory minimum", not as being exhaustive.
Solution: using **kwargs
?
One "solution" could be to use **kwargs
in both abstract and concrete methods to accept any other keyword argument, but the issue is precisely that: the class now accepts any other keyword argument, instead of enforcing specific ones, which brings at least two issues:
- argument names validation can now happen only at runtime (e.g. no
mypy
) - can't know what argument names to use based on method signature alone
class UglyBase:
@abstractmethod
def __init__(self, required: str, **kwargs):
pass
class Ugly(UglyBase):
def __init__(self, required: str, **kwargs):
# yurk
self.something_else = kwargs.get("something_else", "default")
# small typo in the last argument's name goes undetected
# neither IDE nor MyPy can detect it statically
ugly_object = Ugly(required="hello", someting_else="blabla")
# mistyped argument was silently ignored
ugly_object.something_else
>>> "default"