How to reduce the DRY violation in this code
Chris Angelico
rosuav at gmail.com
Tue Sep 27 12:39:28 EDT 2016
On Wed, Sep 28, 2016 at 1:49 AM, Steve D'Aprano
<steve+python at pearwood.info> wrote:
> @classmethod
> def from_strings(cls, bashful='10.0', doc='20.0', dopey='30.0',
> grumpy='40', happy='50', sleepy='60', sneezy='70'):
> bashful = float(bashful)
> doc = float(doc)
> dopey = float(dopey)
> grumpy = int(grumpy)
> happy = int(happy)
> sleepy = int(sleepy)
> sneezy = int(sneezy)
> return cls(bashful, doc, dopey, grumpy, happy, sleepy, sneezy)
>
>
> That's a pretty ugly DRY violation. Imagine that I change the default value
> for bashful from 10.0 to (let's say) 99. I have to touch the code in three
> places (to say nothing of unit tests):
>
> - modify the default value in __init__
> - modify the stringified default value in from_strings
> - change the conversion function from float to int in from_strings
>
>
> Not to mention that each parameter is named seven times.
>
You could go data-driven. Assuming that all your default values are in
the appropriate type (eg you use 10.0 rather than 10, when you want a
float), you could use those directly.
class Spam:
def __init__(self, bashful=10.0, doc=20.0, dopey=30.0,
grumpy=40, happy=50, sleepy=60, sneezy=70):
for name, default in zip(self.__init__.__defaults__,
self.__init__.__code__.co_varnames[1:]):
setattr(self, name, type(default)(locals()[name]))
Your basic __init__ method is now capable of handling strings as well,
so from_strings can simply construct the object directly. I'm not sure
what the advantage of from_strings is, but assuming you still need it,
you could write it thus:
@classmethod
def from_strings(cls, bashful, doc, dopey, grumpy, happy, sleepy, sneezy):
return cls(bashful, doc, dopey, grumpy, happy, sleepy, sneezy)
from_strings.__func__.__defaults__ = tuple(str(x) for x in
__init__.__defaults__)
No duplication of type names or default values, though there is still
duplication of parameter names. You could eliminate that by going
*args,**kw, but at the expense of introspectability. Actually, you
could probably just use wraps...
@classmethod
@functools.wraps(__init__, assigned=())
def from_strings(cls, *a, **kw): return cls(*a, **kw)
from_strings.__func__.__defaults__ = tuple(str(x) for x in
__init__.__defaults__)
Though I'm not particularly enamoured of this way of doing it.
ChrisA
More information about the Python-list
mailing list