Is there are good DRY fix for this painful design pattern?

marco.nawijn at colosso.nl marco.nawijn at colosso.nl
Mon Feb 26 15:09:32 EST 2018


On Monday, February 26, 2018 at 3:44:14 PM UTC+1, Steven D'Aprano wrote:
> I have a class with a large number of parameters (about ten) assigned in 
> `__init__`. The class then has a number of methods which accept 
> *optional* arguments with the same names as the constructor/initialiser 
> parameters. If those arguments are None, the defaults are taken from the 
> instance attributes.
> 
> An example might be something like this:
> 
> 
> class Foo:
>     def __init__(self, bashful, doc, dopey, grumpy, 
>                        happy, sleepy, sneezy):
>         self.bashful = bashful  # etc
> 
>     def spam(self, bashful=None, doc=None, dopey=None, 
>                    grumpy=None, happy=None, sleepy=None,
>                    sneezy=None):
>         if bashful is None:
>             bashful = self.bashful
>         if doc is None:
>             doc = self.doc
>         if dopey is None:
>             dopey = self.dopey
>         if grumpy is None:
>             grumpy = self.grumpy
>         if happy is None:
>             happy = self.happy
>         if sleepy is None:
>             sleepy = self.sleepy
>         if sneezy is None:
>             sneezy = self.sneezy
>         # now do the real work...
> 
>     def eggs(self, bashful=None, # etc... 
>                    ):
>         if bashful is None:
>             bashful = self.bashful
>         # and so on
>  
> 
> There's a lot of tedious boilerplate repetition in this, and to add 
> insult to injury the class is still under active development with an 
> unstable API, so every time I change one of the parameters, or add a new 
> one, I have to change it in over a dozen places.
> 
> Is there a good fix for this to reduce the amount of boilerplate?
> 
> 
> Thanks,
> 
> 
> 
> -- 
> Steve

What about something along these lines:

from inspect import getargspec, getargvalues


class Demo(object):

    def __init__(self, a=None, b=10, c='oops'):
        spec = getargspec(self.__init__)
        for key, value in zip(spec.args[1:], spec.defaults):
            setattr(self, key, value)

    def foo(self, *args, **kwargs):
        assert len(args) == 0

        for k, v in kwargs.items():
            try:
                getattr(self, k)
            except AttributeError:
                print('*** error: attribute {} does not exist.'.format(k))

            setattr(self, k, v)


d = Demo()
print(d.a)
d.foo(a=3.14)
print(d.a)

d.foo(q='this will raise')

So, use the inspect module to detect the valid arguments
from the class initializer. Then use **kwargs in every 
class method. It would be nice if the full method signature
can be kept, but I have not yet figured out if this is
possible. 

Marco



More information about the Python-list mailing list