functions, list, default parameters

Steven D'Aprano steve at REMOVE-THIS-cybersource.com.au
Fri Nov 5 04:58:19 EDT 2010


On Thu, 04 Nov 2010 22:34:07 +0000, Mark Wooding wrote:

> (Based on experience with other languages, I suspect that evaluating the
> default expression afresh for each call where it's needed would be more
> useful; but it's way too late to change that now.)

Let's call the two strategies:

defaults initialise on function definition (DID)
defaults initialise on function call (DIC)

I claim that when designing a general purpose language, DID (Python's 
existing behaviour) is better than DIC:

(1) most function defaults only need to be set once;

(2) it's easy to get DIC if the language uses DID, but hard the other way;

(3) DIC is needless wasteful for common function definitions;

(4) DIC can lead to surprising bugs.

That last isn't a big concern, since DID can lead to surprising bugs as 
well *wink*, but it's worth mentioning the failure mode.

#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.

#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()


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()


#3 Re-initialising default values is wasteful for many functions, perhaps 
the majority of them. (Of course, if you *need* DIC semantics, it isn't 
wasteful, but I'm talking about the situations where you don't care 
either way.) In current Python, nobody would write code like this:

def f(x=None, y=None, z=None):
    if x is None: x = 1
    if y is None: y = 2
    if z is None: z = 3

but that's what the DIC semantics effectively does. When you need it, 
it's useful, but most of the time it's just a performance hit for no good 
reason. A smart compiler would factor out the assignment to a constant 
and do it once, when the function were defined -- which is just what DID 
semantics are.

If you're unconvinced about this being a potential performance hit, 
consider:

def expensive_function():
    time.sleep(30)  # simulate a lot of computation
    return 1.23456789

def f(x=expensive_function()):
    ...

What would you prefer, the default value to be calculated once, or every 
time you called f()?


#4 Just as DID leads to surprising behaviour with mutable defaults, so 
DIC can lead to surprising behaviour:

def f(x=expression):
    do_something_with(x)

If expression is anything except a literal, it could be changed after f 
is defined but before it is called. If so, then f() will change it's 
behaviour.



-- 
Steven



More information about the Python-list mailing list