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