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

Tim Chase python.list at tim.thechases.com
Tue Feb 27 12:47:13 EST 2018


Something like the following might do the trick. As an added benefit,
it's easy to set all the defaults automatically in __init__ as well
without hand-adding "self.dopey = dopey".  On the down side, in the
non-__init__ functions, you have to use kwargs["dopey"] and the like.
It also involves tacking an "__init_args" onto your object so that the
decorator knows what was passed to the __init__ function.

-tkc

from functools import wraps
from inspect import getargspec
def template(original_init_fn):
    args, varargs, keywords, defaults = getargspec(original_init_fn)
    assert varargs is keywords is None
    arg_dict = dict(zip(args[-len(defaults):], defaults))
    @wraps(original_init_fn)
    def new_init_fn(self, *args, **kwargs):
        self.__init_args = arg_dict.copy()
        self.__init_args.update(kwargs)
        # if you don't want to automatically set attributes
        # remove these next two lines
        for k, v in self.__init_args.items():
            setattr(self, k, v)
        return original_init_fn(self, *args, **kwargs)
    def templatify(fn):
        @wraps(fn)
        def new_templated_fn(self, *args, **kwargs):
            for k, v in self.__init_args.items():
                if k not in kwargs:
                    kwargs[k] = v
            return fn(self, *args, **kwargs)
        return new_templated_fn
    new_init_fn.templatify = templatify
    return new_init_fn

class Foo:
    @template
    def __init__(self,
            bashful=None,
            dopey=None,
            doc="On definition",
            ):
        pass # look, ma, no manual assignment!

    @__init__.templatify
    def myfunc(self, **kwargs):
        print(kwargs)

f1 = Foo()
f2 = Foo(bashful="on init", dopey="on init")

for fn in (f1, f2):
    fn.myfunc()
    fn.myfunc(bashful="on myfunc")







On 2018-02-26 14:41, 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
> 
> -- 
> https://mail.python.org/mailman/listinfo/python-list



More information about the Python-list mailing list