[Python-ideas] Default arguments in Python - the return

Steven D'Aprano steve at pearwood.info
Sun May 10 03:23:36 CEST 2009


On Sun, 10 May 2009 10:19:01 am Tennessee Leeuwenburg wrote:
> Hi Pascal,
> Taking the example of
>
> def foo(bar = []):
>   bar.append(4)
>   print(bar)
>
> I'm totally with you in thinking that what is 'natural' is to expect
> to get a new, empty, list every time. 

That's not natural to me. I would be really, really surprised by the 
behaviour you claim is "natural":

>>> DEFAULT = 3
>>> def func(a=DEFAULT):
...     return a+1
... 
>>> func()
4
>>> DEFAULT = 7
>>> func()
8

For deterministic functions, the same argument list should return the 
same result each time. By having default arguments be evaluated every 
time they are required, any function with a default argument becomes 
non-deterministic. Late evaluation of defaults is, essentially, 
equivalent to making the default value a global variable. Global 
variables are rightly Considered Harmful: they should be used with 
care, if at all.


> However this isn't want 
> happens. As far as I'm concerned, that should more or less be the end
> of the discussion in terms of what should ideally happen.

As far as I'm concerned, what Python does now is the idea behaviour. 
Default arguments are part of the function *definition*, not part of 
the body of the function. The definition of the function happens 
*once* -- the function isn't recreated each time you call it, so 
default values shouldn't be recreated either.


> The responses to the change in behaviour which I see as more natural
> are, to summarise, as follows:
>   -- For all sorts of technical reasons, it's too hard
>   -- It changes the semantics of the function definition being
> evaluated at compile time
>   -- It's not what people are used to

And it's not what many people want.

You only see the people who complain about this feature. For the 
multitude of people who expect it or like it, they have no reason to 
say anything (except in response to complaints). When was the last time 
you saw somebody write to the list to say "Gosh, I really love that 
Python uses + for addition"? Features that *just work* never or rarely 
get mentioned.


> With regards to the second point, it's not like the value of
> arguments is set at compile time, so I don't really see that this
> stands up.

I don't see what relevance that has. If the arguments are provided at 
runtime, then the default value doesn't get used.


> I don't think it's intuitive, 

Why do you think that intuitiveness is more valuable than performance 
and consistency?

Besides, intuitiveness is a fickle thing. Given this pair of functions:

def expensive_calculation():
    time.sleep(60)
    return 1

def useful_function(x=expensive_calculation()):
    return x + 1

I think people would be VERY surprised that calling useful_function() 
with no arguments would take a minute *every time*, and would complain 
that this slowness was "unintuitive".


> it's just that people become  
> accustomed to it. There is indeed, *some sense* in understanding that
> the evaluation occurs at compile-time, but there is also a lot of
> sense (and in my opinion, more sense) in understanding the evaluation
> as happening dynamically when the function is called.

No. The body of the function is executed each time the function is 
called. The definition of the function is executed *once*, at compile 
time. Default arguments are part of the definition, not the body, so 
they too should only be executed once. If you want them executed every 
time, put them in the body:

def useful_function(x=SENTINEL):
    if x is SENTINEL:
        x = expensive_calculation()
    return x+1



> With regards to the first point, I'm not sure that this is as
> significant as all of that, although of course I defer to the
> language authors here. However, it seems as though it could be no
> more costly than the lines of code which most frequently follow to
> initialise these variables.
>
> On the final point, that's only true for some people. For a whole lot
> of people, they stumble over it and get it wrong. It's one of the
> most un-Pythonic things which I have to remember about Python when
> programming -- a real gotcha.

I accept that it is a Gotcha. The trouble is, the alternative behaviour 
you propose is *also* a Gotcha, but it's a worse Gotcha, because it 
leads to degraded performance, surprising introduction of global 
variables where no global variables were expected, and a breakdown of 
the neat distinction between creating a function and executing a 
function.

But as for it being un-Pythonic, I'm afraid that if you really think 
that, your understanding of Pythonic is weak. From the Zen:

The Zen of Python, by Tim Peters

Special cases aren't special enough to break the rules.
Although practicality beats purity.
If the implementation is hard to explain, it's a bad idea.

(1) Assignments outside of the body of a function happen once, at 
compile time. Default values are outside the body of the function. You 
want a special case for default values so that they too happen at 
runtime. That's not special enough to warrant breaking the rules.

(2) The potential performance degradation of re-evaluating default 
arguments at runtime is great. For practical reasons, it's best to 
evaluate them once only.

(3) In order to get the behaviour you want, the Python compiler would 
need a more complicated implementation which would be hard to explain.


> I don't see it as changing one way of 
> doing things for another equally valid way of doing things, but
> changing something that's confusing and unexpected for something
> which is far more natural and, to me, Pythonic.

I'm sorry, while re-evaluation of default arguments is sometimes useful, 
it's more often NOT useful. Most default arguments are simple objects 
like small ints or None. What benefit do you gain from re-evaluating 
them every single time? Zero benefit. (Not much cost either, for simple 
cases, but no benefit.)

But for more complex cases, there is great benefit to evaluating default 
arguments once only, and an easy work-around for those rare cases that 
you do want re-evaluation.


> For me, Python 3k appears to be a natural place to do this. Python 3
> still appears to be regarded as a work-in-progress by most people,
> and I don't think that it's 'too late' to change for Python 3k.

Fortunately you're not Guido, and fortunately this isn't going to 
happen. I recommend you either accept that this behaviour is here to 
stay, or if you're *particularly* enamoured of late evaluation 
behaviour of defaults, that you work on some sort of syntax to make it 
optional.



-- 
Steven D'Aprano



More information about the Python-ideas mailing list