Why are default arguments evaluated at definition time?

Viewed 7459

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.

9 Answers

I'll provide a dissenting opinion, by addessing the main arguments in the other posts.

Evaluating default arguments when the function is executed would be bad for performance.

I find this hard to believe. If default argument assignments like foo='some_string' really add an unacceptable amount of overhead, I'm sure it would be possible to identify assignments to immutable literals and precompute them.

If you want a default assignment with a mutable object like foo = [], just use foo = None, followed by foo = foo or [] in the function body.

While this may be unproblematic in individual instances, as a design pattern it's not very elegant. It adds boilerplate code and obscures default argument values. Patterns like foo = foo or ... don't work if foo can be an object like a numpy array with undefined truth value. And in situations where None is a meaningful argument value that may be passed intentionally, it can't be used as a sentinel and this workaround becomes really ugly.

The current behaviour is useful for mutable default objects that should be shared accross function calls.

I would be happy to see evidence to the contrary, but in my experience this use case is much less frequent than mutable objects that should be created anew every time the function is called. To me it also seems like a more advanced use case, whereas accidental default assignments with empty containers are a common gotcha for new Python programmers. Therefore, the principle of least astonishment suggests default argument values should be evaluated when the function is executed.

In addition, it seems to me that there exists an easy workaround for mutable objects that should be shared across function calls: initialise them outside the function.

So I would argue that this was a bad design decision. My guess is that it was chosen because its implementation is actually simpler and because it has a valid (albeit limited) use case. Unfortunately, I don't think this will ever change, since the core Python developers want to avoid a repeat of the amount of backwards incompatibility that Python 3 introduced.

Related