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?
I have not yet looked into dataclasses. Don't they handle the __init__() part? Anyway, here's my attempt to make spam() less spammy: $ cat attrs_to_args_decorator.py import functools import inspect def add_defaults(f): argnames = inspect.getfullargspec(f).args[1:] @functools.wraps(f) def wrapper(self, *args, **kw): args = [ getattr(self, name) if value is None else value for name, value in zip(argnames, args) ] for name in argnames[len(args):]: if name not in kw or kw[name] is None: kw[name] = getattr(self, name) return f(self, *args, **kw) return wrapper def update_attrs(kw): self = kw.pop("self") for name, value in kw.items(): setattr(self, name, value) class Foo: def __init__(self, bashful, doc, dopey, grumpy, happy, sleepy, sneezy): update_attrs(locals()) @add_defaults def spam(self, bashful=None, doc=None, dopey=None, grumpy=None, happy=None, sleepy=None, sneezy=None): return "{}-{}-{}".format(bashful, doc, sneezy) def __repr__(self): return "Foo({})".format( ", ".join( "{}={!r}".format(*pair) for pair in sorted(self.__dict__.items()) ) ) if __name__ == "__main__": foo = Foo("bashful", "doc", "dopey", "grumpy", "happy", "sleepy", "sneezy") print(foo) print(foo.spam()) print(foo.spam(bashful="BASHFUL")) print(foo.spam(bashful="BASHFUL", doc="DOC")) print(foo.spam("BASHFUL")) print(foo.spam("BASHFUL", "DOC")) print(foo.spam("BASHFUL", "DOC", sneezy="SNEEZY")) $ python3 attrs_to_args_decorator.py Foo(bashful='bashful', doc='doc', dopey='dopey', grumpy='grumpy', happy='happy', sleepy='sleepy', sneezy='sneezy') bashful-doc-sneezy BASHFUL-doc-sneezy BASHFUL-DOC-sneezy BASHFUL-doc-sneezy BASHFUL-DOC-sneezy BASHFUL-DOC-SNEEZY $ -- https://mail.python.org/mailman/listinfo/python-list