DRY functions with named attributes used as default arguments

Tim Chase python.list at tim.thechases.com
Sun Oct 9 14:57:03 EDT 2011


On 10/09/11 10:37, Steven D'Aprano 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?

As you later ask...

>> 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?

By indirecting through DEFAULT, I can change DEFAULT to point at 
another behavior-tweaking option in one place ("DEFAULT = 
SPECIAL") rather than in multiple places.  However, I can't give 
a very good argument for just using

   def foo(arg1, arg2=SPECIAL)

and then, if it changes, just change *that* one location to

   def foo(arg1, arg2=MONKEY)

because, well, Python calls them default arguments for a reason :)

> class Foo:
>      SPECIAL = 42
>      MONKEY = 31215
>      DEFAULT = SPECIAL
>      def __call__(self, arg1, arg2=DEFAULT):
>          ...
>
> foo = Foo()
> del Foo

I did consider this (sorry I forgot to mention it) and it works 
well too. It's a little cleaner, as the magic happens in 
something named __call__ which is more obvious than overloading 
odd behavior into __new__.  The instantiate-and-delete-the-class 
felt a little weird, and having both the class and the instance 
in the namespace felt weird.  Granted the (ab)use of __new__ felt 
weird too, so neither wins by great margin.  Which is part of my 
question: what's the least-worst way to do this? :)
>> 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.

I guess part of my attempt was to keep from littering the module 
namespace with things that only apply to the one function (and 
this also applies to C-like prefixes such as FOO_SPECIAL)

>> 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.

The code felt like it was riding the WTF-boundary, so I find your 
evaluation of "unusual but not WTF" encouraging.

Thanks for your thoughts,

-tkc







More information about the Python-list mailing list