Why Are Default Arguments Evaluated at Definition Time

Why are default arguments evaluated at definition time?

The alternative would be quite heavyweight -- storing "default argument values" in the function object as "thunks" of code to be executed over and over again every time the function is called without a specified value for that argument -- and would make it much harder to get early binding (binding at def time), which is often what you want. For example, in Python as it exists:

def ack(m, n, _memo={}):
key = m, n
if key not in _memo:
if m==0: v = n + 1
elif n==0: v = ack(m-1, 1)
else: v = ack(m-1, ack(m, n-1))
_memo[key] = v
return _memo[key]

...writing a memoized function like the above is quite an elementary task. Similarly:

for i in range(len(buttons)):
buttons[i].onclick(lambda i=i: say('button %s', i))

...the simple i=i, relying on the early-binding (definition time) of default arg values, is a trivially simple way to get early binding. So, the current rule is simple, straightforward, and lets you do all you want in a way that's extremely easy to explain and understand: if you want late binding of an expression's value, evaluate that expression in the function body; if you want early binding, evaluate it as the default value of an arg.

The alternative, forcing late binding for both situation, would not offer this flexibility, and would force you to go through hoops (such as wrapping your function into a closure factory) every time you needed early binding, as in the above examples -- yet more heavy-weight boilerplate forced on the programmer by this hypothetical design decision (beyond the "invisible" ones of generating and repeatedly evaluating thunks all over the place).

In other words, "There should be one, and preferably only one, obvious way to do it [1]": when you want late binding, there's already a perfectly obvious way to achieve it (since all of the function's code is only executed at call time, obviously everything evaluated there is late-bound); having default-arg evaluation produce early binding gives you an obvious way to achieve early binding as well (a plus!-) rather than giving TWO obvious ways to get late binding and no obvious way to get early binding (a minus!-).

[1]: "Although that way may not be obvious at first unless you're Dutch."

Python Default Arguments Evaluation

When you define a function the values of the default arguments get evaluated, but the body of the function only get compiled. You can examine the result of the function definition via attributes. Theres a __defaults__ attribute containing the defaults and __code__ attribute containing the body (so these are created when the function is defined).

What's happening in the second example is that None do get evaluated at definition (it evaluates to None duh!), but the code that conditionally assigns [] to L only gets compiled and is run each time (the condition passes).

Default arguments have to be bound at compiled time - why?

Actually, that's not completely accurate. The restrictions are:

8.3.6 Default arguments [dcl.fct.default]

7) Local variables shall not be used in a default argument. [ Example:

void f() {
int i;
extern void g(int x = i); //error
// ...
}

—end example ]

8) The keyword this shall not be used in a default argument of a member function. [ Example:

class A {
void f(A* p = this) { } // error
};

So, this and local variables can't be used as defaults.

For example, the following is valid:

int a = 1;
int f(int);
int g(int x = f(a)); // default argument: f(::a)
void h() {
a = 2;
{
int a = 3;
g(); // g(f(::a))
}
}

g will be called with the value f(2), which isn't a compile-time constant. This is an example straight from the standard.

The reasons it's like this is the usual: there either wasn't a proposal for it, or it was rejected, deemed not necessary or too difficult to implement.

Least Astonishment and the Mutable Default Argument

Actually, this is not a design flaw, and it is not because of internals or performance. It comes simply from the fact that functions in Python are first-class objects, and not only a piece of code.

As soon as you think of it this way, then it completely makes sense: a function is an object being evaluated on its definition; default parameters are kind of "member data" and therefore their state may change from one call to the other - exactly as in any other object.

In any case, the effbot (Fredrik Lundh) has a very nice explanation of the reasons for this behavior in Default Parameter Values in Python.
I found it very clear, and I really suggest reading it for a better knowledge of how function objects work.

How to get a warning about a list being a mutable default argument?

flake8-bugbear, Pylint, PyCharm, and Pyright can detect this:

  • Bugbear has B006 (Do not use mutable data structures for argument defaults).

    Do not use mutable data structures for argument defaults. They are created during function definition time. All calls to the function reuse this one instance of that data structure, persisting changes between them.

  • Pylint has W0102 (dangerous default value).

    Used when a mutable value as list or dictionary is detected in a default value for an argument.

  • Pyright has reportCallInDefaultInitializer.

    Generate or suppress diagnostics for function calls, list expressions, set expressions, or dictionary expressions within a default value initialization expression. Such calls can mask expensive operations that are performed at module initialization time.

    This does what you want, but be aware that it also checks for function calls in default arguments.

  • PyCharm has Default argument's value is mutable.

    This inspection detects when a mutable value as list or dictionary is detected in a default value for an argument.

    Default argument values are evaluated only once at function definition time, which means that modifying the default value of the argument will affect all subsequent calls of the function.

    Unfortunately, I can't find online documentation for this. If you have PyCharm, you can access all inspections and navigate to this inspection to find the documentation.

Evaluation order of function arguments and default arguments

The order of evaluation (i.e. determining the value) of function arguments is not specified. The compiler is free to execute them in any order, and even intermingled if there are no other factors stopping it from doing so.

Evaluation of default arguments happens in the context of the caller, not the callee. So the call to f() is necessary for one argument, and reading the global variable p for the other. Which order this happens in is not specified, so the global could be read before or after the call to f().

Python: Optional parameter, wrong default value

The default value is evaluated and stored once when the function is defined. It is not re-checked each time the function is called. This will work as you expect:

default_var = "abc"

def set_default_var():
global default_var
default_var = "something different"

def ex_func(var1="", var2="", var3=None):
if var3 == None:
var3 = default_var
print(var3)

set_default_var()
ex_func()

Python function: Optional argument evaluated once?

In Python, functions are objects too, and the defaults are stored with the function object. Defaults are not locals; it is just that when the function is called, the arguments are bound to a default when not given an explicit value.

When Python encounters a def <functionname>(<arguments>): statement, it creates a function object for you there and then; this is 'definition time'; the function is not called but merely created. It is then that defaults are evaluated and stored, in an attribute on the function object.

Then when you call the function, the defaults have already been created and are used when you didn't provide a more concrete value for the argument. Because the defaults are stored with the function object, you get to see changes to mutable objects between function calls.

The locals are still cleared up of course, but as they are references (all identifiers in Python are), the objects they were bound to are only cleared up if nothing else is referencing them anymore either.

You can take a look a the defaults of any function object:

>>> def foo(bar='spam', eggs=[]):
... eggs.append(bar)
... return eggs
...
>>> foo.__defaults__
('spam', [])
>>> foo()
['spam']
>>> foo.__defaults__
('spam', ['spam'])
>>> foo() is foo.__defaults__[1]
True

The foo() function has a __defaults__ attribute, a tuple of default values to use when no values for the arguments have been passed in. You can see the mutable list change as the function is called, and because the function returns the eggs list, you can also see that it is the exact same object as the second value in that tuple.

If you don't want your defaults to be shared and instead need a new value for a parameter every time the function is called, but the parameter is not given, you need to set the default value to a sentinel object. If your parameter is still set to that sentinel in the function body, you can execute code to set a fresh default value. None is usually the best choice:

def foo(bar='spam', eggs=None):
if eggs is None:
eggs = []

If it should be possible to use None as a non-default value, use a singleton sentinel created beforehand:

_sentinel = object()

def foo(bar='spam', eggs=_sentinel):
if eggs is _sentinel:
eggs = []


Related Topics



Leave a reply



Submit