DRY functions with named attributes used as default arguments
Steven D'Aprano
steve+comp.lang.python at pearwood.info
Sun Oct 9 11:37:57 EDT 2011
Tim Chase wrote:
> My intent is to have a function object something like
>
> def foo(arg1, arg2=foo.DEFAULT):
> return int(do_stuff(arg1, arg2))
> foo.SPECIAL = 42
> foo.MONKEY = 31415
> foo.DEFAULT = foo.SPECIAL
What's the purpose of having both foo.SPECIAL and foo.DEFAULT?
You could always use a callable instance instead of a function, what C++
calls a functor (not to be confused with what Haskell calls a functor,
which is completely different).
class Foo:
SPECIAL = 42
MONKEY = 31215
DEFAULT = SPECIAL
def __call__(self, arg1, arg2=DEFAULT):
...
foo = Foo()
del Foo
The default value of arg2 is bound at class definition time, once. If you
prefer late binding instead of early binding, it is easy to put off the
assignment until the function is called:
def __call__(self, arg1, arg2=None):
if arg2 is None:
arg2 = self.DEFAULT
...
If None is a legitimate data value for arg2, you can create your own
sentinel:
SENTINEL = object()
and use that instead of None.
> so I can call it with either
>
> result = foo(myarg)
>
> or
>
> result = foo(myarg, foo.SPECIAL)
>
> However I can't do this because foo.DEFAULT isn't defined at the
> time the function is created. I'd like to avoid hard-coding
> things while staying DRY, so I don't like
>
> def foo(arg1, arg2=42)
>
> because the default might change due to business rule changes,
If the business rule changes, you have to change foo.DEFAULT anyway. So why
not cut out the middle man and change the default argument in the function
signature?
> I
> have a dangling "magic constant" and if the value of SPECIAL
> changes, I have to catch that it should be changed in two places.
Then put it in one place.
SPECIAL = 42
def foo(arg1, arg2=SPECIAL):
...
and avoid the reference to foo.
> My current hack/abuse is to use __new__ in a class that can
> contain the information:
>
> class foo(object):
> SPECIAL = 42
> MONKEY = 31415
> DEFAULT = SPECIAL
> def __new__(cls, arg1, arg2=DEFAULT):
> return int(do_stuff(arg1, arg2))
>
> i1 = foo("spatula")
> i2 = foo("tapioca", foo.MONKEY)
>
> 1) is this "icky" (a term of art ;-)
> 2) or is this reasonable
Seems okay to me. A little unusual, but only a little, not "WTF is this code
doing???" territory.
--
Steven
More information about the Python-list
mailing list