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