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