Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I know that default arguments are created at the function initialisation time and not every time the function is called. See the following code:

def ook (item, lst=[]):
    lst.append(item)
    print 'ook', lst

def eek (item, lst=None):
    if lst is None: lst = []
    lst.append(item)
    print 'eek', lst

max = 3
for x in xrange(max):
    ook(x)

for x in xrange(max):
    eek(x)

What I do not get is why this was implemented this way. What benefits does this behaviour offers over an initialisation at each call time?

share|improve this question
add comment

5 Answers

up vote 8 down vote accepted

I think the reason is implementation simplicity. Let me elaborate.

The default value of the function is an expression that you need to evaluate. In your case it is a simple expression that does not depend on the closure, but it can be something that contains free variables - def ook(item, lst = something.defaultList()). If you are to design Python, you will have a choice - do you evaluate it once when the function is defined or every time when the function is called. Python chooses the first (unlike Ruby, which goes with the second option).

There are some benefits for this.

First, you get some speed and memory boosts. In most cases you will have immutable default arguments and Python can construct them just once, instead of on every function call. This saves (some) memory and time. Of course, it doesn't work quite well with mutable values, but you know how you can go around.

Another benefit is the simplicity. It's quite easy to understand how the expression is evaluated - it uses the lexical scope when the function is defined. If they went the other way, the lexical scope might change between the definition and the invocation and make it a bit harder to debug. Python goes a long way to be extremely straightforward in those cases.

share|improve this answer
1  
Interesting point - though it's usually the principle of least surprise with Python. Some things are simple in a formal complexity-of-the-model sense, yet non-obvious and surprising, and I think this counts. –  Steve314 Jul 19 '12 at 10:57
    
The thing about the least surprise here is this: if you have the evaluate-on-every-call semantics, you can get an error if the closure changes between two function calls (which is quite possible). This can be more surprising as opposed to knowing that it is evaluated once. Of course, one can argue that when you come from other languages, you except evaluate-on-each-call semantics and that is the surprise, but you can see how it goes both ways :) –  Stefan Kanev Jul 19 '12 at 11:58
    
Good point about the scope –  0xc0de Jul 26 '12 at 7:16
add comment

One way to put it is that the lst.append(item) doesn't mutate the lst parameter. lst still references the same list. It's just that the content of that list has been mutated.

Basically, Python doesn't have (that I recall) any constant or immutable variables at all - but it does have some constant, immutable types. You can't modify an integer value, you can only replace it. But you can modify the content of a list without replacing it.

Like an integer, you can't modify a reference, you can only replace it. But you can modify the content of the object being referenced.

As for creating the default object once, I imagine that's mostly as an optimisation, to save on object-creation and garbage collection overheads.

share|improve this answer
    
+1 Exactly. It's important to understand the layer of indirection - that a variable isn't a value; instead it references a value. To change a variable, the value can be swapped, or mutated (if it's mutable). –  Joonas Pulakka Jul 19 '12 at 10:42
add comment

What benefits does this behaviour offers over an initialisation at each call time?

It lets you select the behavior you want, as you demonstrated in your example. So if you want that the default argument is immutable, you use an immutable value, such as None or 1. If you want to make the default argument mutable, you use something mutable, such as []. It's just flexibility, albeit admittedly, it can bite if you don't know it.

share|improve this answer
add comment

I think the real answer is: Python was written as a procedural language and only adopted functional aspects after-the-fact. What you're looking for is for parameter defaulting to be done as a closure, and closures in Python are really only half-baked. For evidence of this try:

a = []
for i in range(3):
    a.append(lambda: i)
print [ f() for f in a ]

which gives [2, 2, 2] where you would expect a true closure to produce [0, 1, 2].

There are quite a lot of things that I'd like if Python had the ability to wrap parameter defaulting in closures. For example:

def foo(a, b=a.b):
    ...

Would be extremely handy, but "a" isn't in scope at function definition time, so you can't do that and instead have to do the clunky:

def foo(a, b=None):
    if b is None:
        b = a.b

Which is almost the same thing... almost.

share|improve this answer
add comment

This happens because functions in Python are first-class objects:

Default parameter values are evaluated when the function definition is executed. This means that the expression is evaluated once, when the function is defined, and that the same “pre-computed” value is used for each call.

It goes on to explain that editing the parameter value modifies the default value for subsequent calls, and that a simple solution of using None as the default, with an explicit test in the function body, is all that is needed to ensure no surprises.

Which means that def foo(l=[]) becomes an instance of that function when called, and gets reused for further calls. Think of function parameters as becoming apart of an object's attributes.

Pro's could include leveraging this to have classes have C-like static variables. So it's best to declare default values None and initialize them as needed:

class Foo(object):
    def bar(self, l=None):
        if not l:
            l = []
        l.append(5)
        return l

f = Foo()
print(f.bar())
print(f.bar())

g = Foo()
print(g.bar())
print(g.bar())

yields:

[5] [5] [5] [5]

instead of the unexpected:

[5] [5, 5] [5, 5, 5] [5, 5, 5, 5]

share|improve this answer
3  
No. You could define functions (first-class or not) differently to evaluate the default argument expression again for each call. And everything after that, i.e. roughly 90% of the answer, is completely beside the question. -1 –  delnan Jul 20 '12 at 15:57
1  
So then share with us this knowledge of how to define functions to evaluate the default arg for each call, I'd like to know of a simpler way than the Python Docs recommends. –  invert Jul 23 '12 at 10:18
    
On a language design level I mean. The Python language definition currently states that default arguments are treated the way they are; it could equally well state that default arguments are treated in some other way. IOW you are answering "that's how things are" to the question "why are things the way they are". –  delnan Jul 23 '12 at 16:42
    
Python could have implemented default parameters similar to how Coffeescript does it. It would insert bytecode to check for missing parameters, and if they were missing would evaluate the expression. –  Winston Ewert Aug 4 '12 at 16:20
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.