functions, list, default parameters

Steven D'Aprano steve at REMOVE-THIS-cybersource.com.au
Fri Nov 5 23:33:08 EDT 2010


On Fri, 05 Nov 2010 12:17:00 +0000, Mark Wooding wrote:

>> #1 Most default values are things like True, False, None, integer or
>> string literals. Since they're literals, they will never change, so you
>> only need to set them once.
> 
> Right; so a half-decent compiler can notice this and optimize
> appropriately.  Result: negligible difference.

You're right, of course, a sufficiently smart compiler could do this. It 
might even be worth doing in a language that otherwise re-initialises 
function defaults on every call. But such optimizations don't happen for 
free, they have costs: somebody has to write it, debug it, maintain it, 
deal with the added complexity. Python -- at least CPython -- tends to go 
for the simplest compiler that will work.

Perhaps the biggest cost is that now your language has inconsistent 
semantics: some function defaults are set on every call, and some are set 
once, when the function is defined, and the choice between the two 
happens via "magic" -- the compiler decides what to do, you don't.

I have mixed feelings about compiler optimizations. Things like constant 
folding seems to be both harmless and useful, but other optimizations not 
so much. It sets up a discrepancy between what the source code does and 
what the compiled code does, and in the case of (say) floating point 
code, can introduce *serious* bugs. So while I like the idea of compiler 
optimizations is principle, in practice I tend to think they should be 
avoided.


>> #2 It's easy to get default values to initialise on function call in a
>> language that uses initialisation on function definition semantics:
>> just move the initialisation into the function body. Python has a
>> particularly short and simple idiom for it:
>>
>> def f(x=None):
>>     if x is None:
>>         x = some_expression()
> 
> That's actually rather clumsy.

You think so? I think it's quite simple, obvious and neat.


> Also, it's using in-band signalling:
> you've taken None and used it as a magic marker meaning `not supplied'.
> Suppose you want to distinguish /any/ supplied value from a missing
> argument: how do you do this /correctly/?
> 
> The approaches I see are (a) to invent some unforgeable token 

That's the simplest way.

_sentinel = object()

def f(x=_sentinel):
    if x is _sentinel: x = some_expression()


> or (b)
> emulate the argument processing by rifling through * and ** arguments.
> 
> Solution (a) looks like this:
> 
>         _missing = ['missing']
>         def foo(arg = _missing):
>           if arg is _missing: arg = ...
>           ...

A curious choice for the sentinel value. We're not using Python 1.5 any 
longer :)


> But now _missing is kicking around in the module's namespace.

Er, yes. That's what namespaces are for.


>> But if the situations were reversed, it's hard to get the DID
>> semantics:
>>
>> def f(x=None):
>>     if x is None:
>>         global _f_default_arg
>>         try:
>>             x = _f_default_arg
>>         except NameError:
>>             _f_default_arg = x = default_calculation()
> 
> Ugh.  This is artificially awful and doesn't correspond to existing
> Python DID semantics.
[snip]

Yes, you're right, those are good points and I stand corrected.



-- 
Steven



More information about the Python-list mailing list