Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are default arguments evaluated at definition time?

I had a very difficult time with understanding the root cause of a problem in an algorithm. Then, by simplifying the functions step by step I found out that evaluation of default arguments in Python doesn't behave as I expected.

The code is as follows:

class Node(object):     def __init__(self, children = []):         self.children = children 

The problem is that every instance of Node class shares the same children attribute, if the attribute is not given explicitly, such as:

>>> n0 = Node() >>> n1 = Node() >>> id(n1.children) Out[0]: 25000176 >>> id(n0.children) Out[0]: 25000176 

I don't understand the logic of this design decision? Why did Python designers decide that default arguments are to be evaluated at definition time? This seems very counter-intuitive to me.

like image 997
Mert Nuhoglu Avatar asked Oct 30 '09 17:10

Mert Nuhoglu


People also ask

What is the purpose of default function argument?

A default argument is a value provided in a function declaration that is automatically assigned by the compiler if the calling function doesn't provide a value for the argument. In case any value is passed, the default value is overridden.

In what situations default arguments are useful?

We cannot provide a default value to a particular argument in the middle of an argument list. D. Default arguments are useful in situations where some arguments always have the same value.

How do you define a default argument in Python?

Python has a different way of representing syntax and default values for function arguments. Default values indicate that the function argument will take that value if no argument value is passed during the function call. The default value is assigned by using the assignment(=) operator of the form keywordname=value.


2 Answers

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

like image 87
Alex Martelli Avatar answered Oct 19 '22 07:10

Alex Martelli


The issue is this.

It's too expensive to evaluate a function as an initializer every time the function is called.

  • 0 is a simple literal. Evaluate it once, use it forever.

  • int is a function (like list) that would have to be evaluated each time it's required as an initializer.

The construct [] is literal, like 0, that means "this exact object".

The problem is that some people hope that it to means list as in "evaluate this function for me, please, to get the object that is the initializer".

It would be a crushing burden to add the necessary if statement to do this evaluation all the time. It's better to take all arguments as literals and not do any additional function evaluation as part of trying to do a function evaluation.

Also, more fundamentally, it's technically impossible to implement argument defaults as function evaluations.

Consider, for a moment the recursive horror of this kind of circularity. Let's say that instead of default values being literals, we allow them to be functions which are evaluated each time a parameter's default values are required.

[This would parallel the way collections.defaultdict works.]

def aFunc( a=another_func ):     return a*2  def another_func( b=aFunc ):     return b*3 

What is the value of another_func()? To get the default for b, it must evaluate aFunc, which requires an eval of another_func. Oops.

like image 27
S.Lott Avatar answered Oct 19 '22 07:10

S.Lott