What is a function parameter =[] for?

Steven D'Aprano steve at pearwood.info
Tue Nov 24 12:29:52 EST 2015


On Tue, 24 Nov 2015 11:38 pm, Antoon Pardon wrote:

> Op 19-11-15 om 13:45 schreef Steven D'Aprano:
[...]
>> I don't mean that it isn't sometimes useful. Of course it is sometimes
>> useful, there's no doubt about that. But why would you expect the
>> language to default to the *slow*, *expensive*, *complicated* behaviour
>> instead of the *fast*, *cheap*, *simple* behaviour?
> 
> I would say because it is better understandable. 

I disagree that it (late-binding) is more understandable. 

Late-binding (or, "late evaluation" for those who prefer that term) is no
easier to understand than early binding. Earlier, I publicly screwed up
reasoning about late binding.

And so did BartC, whose personal language has precisely the late binding
semantics he wants, exactly as he designed it, and yet he was surprised by
its behaviour leading him to (wrongly) conclude that his language was more
dynamic than Python:

Quote:

    "(Python returns 42; so that means my languages are more dynamic than 
    Python? That's hard to believe!)"

See this thread, post from BartC dated Fri, 20 Nov 2015 08:21:24 am.


> The way default value 
> behave now, is just a little less surprising as when the following segment
> of code would print: [1]
> 
> a = []
> a.append(1)
> b = []
> print b

No, it really isn't anything like that at all. If you (generic you, not
specifically you personally) think that it is similar, you've misunderstood
what is going on.

If we want an analogy, this is a good one:

alist = []
for i in range(20):
    alist.append(i)
    print alist


Are you surprised that alist keeps growing? Contrast this to:


for i in range(20):
    alist = []
    alist.append(i)
    print alist


Are you surprised that alist keeps getting re-set to the empty list?


Change the for-loop to a function declaration. If the binding of the list []
happens inside the indented block, it will happen every time the function
is called (just as it happens each time through the loop in the second
example above). If the binding happens outside of the body, it happens
once, not every time.

Folk like BartC want the function default to be re-evaluated every time the
function is called. I'm not blind to the usefulness of it: I write lots of
functions with late-binding semantics too. I just write them like this:

def func(a=None):
    if a is None:
        a = something()


Perhaps as more people start using function annotations, it will become more
obvious that function declarations aren't re-evaluated every single time
you call the function:

def func(a:some_expression=another_expression): ...

*Both* expressions, the annotation and the default, are evaluated once and
once only.


> Let us not forget that the tutorial talks about Default Argument *Values*.
> And also the language reference talks about precomputed *values*. What
> we really get is a precomputed object.

There is a distinction between value and object, but without seeing the
exact wording and context of the tutorial and language reference, I cannot
tell whether they are misleading or not. There's nothing wrong with talking
about default values. You just have to remember that some values can
change.


>> You already have one way to set the argument to a fresh list every time
>> you call the function: put the code you want executed inside the body of
>> the function. There's no need for a *second* way to get the same result.
> 
> Which is beside the point. The point is understandability.

Okay. Suppose for the sake of the argument I agree with you
that "understandability" is the most important factor here.

Then early binding is still the clear winner, because it has simple, easy to
understand semantics:

- the "def" line is executed once, and once only, including all parameter
defaults and annotations;

- the *body* of the function is executed when the function is called, not
the "def" function declaration.

Since the function defaults are part of the declaration, not the body, it is
far more understandable that it is executed once only.



>> But if you want the default value to be evaluated exactly once, and once
>> only, there is no real alternative to early binding. You could use a
>> global variable, of course, but that is no solution -- that's a problem
>> waiting to happen.
> 
> No more than the situation we have now. The situation we have now is a
> problem waiting to happen too. Which is confirmed again and again. You are
> stacking the deck by calling a possible alternative "a problem waiting to
> happen" while ignoring the problems that happen with the current
> situation.

Pardon me, I certainly am not ignoring anything of the sort. I do
acknowledge that the behaviour of mutable defaults can be surprising. I
even admitted that it surprised me. It's often not the behaviour that you
want, so you have to do something slightly different.

The bottom line is that there are good reasons for the way Python works with
function defaults, but they aren't good for everything. Neither is the
alternative. Whichever design Python used, it would inconvenience people at
some time or another.



-- 
Steven




More information about the Python-list mailing list