ABC with abstractmethod: kwargs on Base, explicit names on implementation

Samuel Marks samuelmarks at gmail.com
Thu Aug 27 01:58:11 EDT 2020


The main thing I want is type safety. I want Python to complain if the
callee uses the wrong argument types, and to provide suggestions on
what's needed and info about it.

Without a base class I can just have docstrings and type annotations
to achieve that.

What can I use that will require all implementers to have a minimum of
the same properties and arguments, but also allow them to add new
properties and arguments?

Preferably I would like this all to happen before compile/interpret
time. This also opens it up to AST driven stub generation; as can be
found in various IDEs (and also a little project I wrote that `import
ast`).

What are my options here? - It doesn't seem like the metaclass or
decorator approaches will help here…

Samuel Marks
Charity <https://sydneyscientific.org> | consultancy
<https://offscale.io> | open-source <https://github.com/offscale> |
LinkedIn <https://linkedin.com/in/samuelmarks>


Samuel Marks wrote at 2020-8-24 18:24 +1000:
>After a discussion on #python on Freenode, I'm here.
>
>The gist of it is:
>> Falc - Signature of method 'Pharm.foo()' does not match signature of base method in class 'Base'
>
>What's the right way of specialising the children and leaving the Base
>pretty much empty?
>
>Specifically I want:
>• All implementers to be forced to implement Base's method
>• Base to be able to do very basic things with kwargs, namely log it
>(to a file like stdout or a db)
>• Support for [at least] Python 3.6–3.9 (I don't think `Protocol` has
>been backported?)
>• Implementors to have access to the proper name, rather than having
>to unpack kwargs
>
>Should I be using a Base class? - Metaclass? - Something else
>entirely? - I know giving `b` a default value would resolve the
>[PyCharm] linter error… but I want it to be a required argument.
>
>Full example:
>
>from abc import ABC, abstractmethod
>
>
>class Base(ABC):
>    @abstractmethod
>    def foo(self, a, **kwargs):
> ...
>class Pharm(Base):
>    def foo(self, a, b):
> ...

Python make a distinction between positional and keyword
arguments. A positional argument is identified by its
position in the parameter list; a keyword argument is
identified by its name.

`**` introduces arbitrary keyword arguments. In a call, all
those arguments must be passed as "name=value".
In your case above, `b` is not a keyword argument and thus
is not matched by `**kwargs`. The error you observe is justified.

You can try:

class Base(ABC):
    @abstractmethod
    def foo(self, a, *args, **kwargs):
        ...

class Pharm(Base):
    def foo(self, a, b, *args, **kwargs):
        ...


Note that the base method signature allows arbitrary positional
and keyword arguments. As a consequence, derived methods must do
the same.

If this is not what you want, you might want to explore
the use of a decorator or a meta class rather than a base class.


More information about the Python-list mailing list