I am a maintainer of a Python project that makes heavy use of inheritance. There's an anti-pattern that has caused us a couple of issues and makes reading difficult, and I am looking for a good way to fix it.
The problem is forwarding very long argument lists from derived classes to base classes - mostly but not always in constructors.
Consider this artificial example:
class Base(object):
def __init__(self, a=1, b=2, c=3, d=4, e=5, f=6, g=7):
self.a = a
# etc
class DerivedA(Base):
def __init__(self, a=1, b=2, c=300, d=4, e=5, f=6, g=700, z=0):
super().__init__(a=a, b=b, c=c, d=d, e=e, f=f, g=g)
self.z = z
class DerivedB(Base):
def __init__(self, z=0, c=300, g=700, **kwds):
super().__init__(c=c, g=g, **kwds)
self.z = z
At this time, everything looks like DerivedA
- long argument lists, all of which are passed to the base class explicitly.
Unfortunately, we've had a couple of issues over the last couple of years, involving forgetting to pass an argument and getting the default, and from not noticing that one default parameter in one derived class was different from the default default.
It also makes the code needlessly bulky and therefore hard-to-read.
DerivedB
is better and fixes those problems, but has the new problem that the Python help/sphinx HTML documentation for the method in the derived class is misleading as a lot of the important parameters are hidden in the **kwds
.
Is there some way to "forward" the correct signature - or at least the documentation of the correct signature - from the base class method to the derived class method?
I haven't found a way to perfectly create a function with the same signature, but I think the downsides of my implementation aren't too serious. The solution I've come up with is a function decorator.
Usage example:
All named inherited parameters will be passed to the function through the
kwargs
dict. Theargs
parameter is only used to pass varargs to the function. If the parent function has no varargs,args
will always be an empty tuple.Known problems and limitations:
function.__code__.co_filename
will be set to"<string>"
.If the decorated functions throws an exception, there will be an additional function call visible in the exception traceback, for example:
>>> f2() Traceback (most recent call last): File "", line 1, in File "", line 3, in f2 File "untitled.py", line 178, in f2 raise ValueError() ValueError
Implementation
Short explanation:
The decorator creates a string of the form
then
exec
s it and returns the dynamically created function.Additional features:
If you don't want to "inherit" parameters x and y, use
Consider using attrs module: