Anyone tinkering with Python long enough has been bitten (or torn to pieces) by the following issue:
def foo(a=[]):
a.append(5)
return a
Python novices would expect this function to always return a list with only one element: [5]
. The result is instead very different, and very astonishing (for a novice):
>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]
>>> foo()
[5, 5, 5, 5]
>>> foo()
A manager of mine once had his first encounter with this feature, and called it "a dramatic design flaw" of the language. I replied that the behavior had an underlying explanation, and it is indeed very puzzling and unexpected if you don't understand the internals. However, I was not able to answer (to myself) the following question: what is the reason for binding the default argument at function definition, and not at function execution? I doubt the experienced behavior has a practical use (who really used static variables in C, without breeding bugs?)
Edit:
Baczek made an interesting example. Together with most of your comments and Utaal's in particular, I elaborated further:
>>> def a():
... print("a executed")
... return []
...
>>>
>>> def b(x=a()):
... x.append(5)
... print(x)
...
a executed
>>> b()
[5]
>>> b()
[5, 5]
To me, it seems that the design decision was relative to where to put the scope of parameters: inside the function or "together" with it?
Doing the binding inside the function would mean that x
is effectively bound to the specified default when the function is called, not defined, something that would present a deep flaw: the def
line would be "hybrid" in the sense that part of the binding (of the function object) would happen at definition, and part (assignment of default parameters) at function invocation time.
The actual behavior is more consistent: everything of that line gets evaluated when that line is executed, meaning at function definition.
This behavior is not surprising if you take the following into consideration:
The role of (2) has been covered extensively in this thread. (1) is likely the astonishment causing factor, as this behavior is not "intuitive" when coming from other languages.
(1) is described in the Python tutorial on classes. In an attempt to assign a value to a read-only class attribute:
Look back to the original example and consider the above points:
Here
foo
is an object anda
is an attribute offoo
(available atfoo.func_defs[0]
). Sincea
is a list,a
is mutable and is thus a read-write attribute offoo
. It is initialized to the empty list as specified by the signature when the function is instantiated, and is available for reading and writing as long as the function object exists.Calling
foo
without overriding a default uses that default's value fromfoo.func_defs
. In this case,foo.func_defs[0]
is used fora
within function object's code scope. Changes toa
changefoo.func_defs[0]
, which is part of thefoo
object and persists between execution of the code infoo
.Now, compare this to the example from the documentation on emulating the default argument behavior of other languages, such that the function signature defaults are used every time the function is executed:
Taking (1) and (2) into account, one can see why this accomplishes the the desired behavior:
foo
function object is instantiated,foo.func_defs[0]
is set toNone
, an immutable object.L
in the function call),foo.func_defs[0]
(None
) is available in the local scope asL
.L = []
, the assignment cannot succeed atfoo.func_defs[0]
, because that attribute is read-only.L
is created in the local scope and used for the remainder of the function call.foo.func_defs[0]
thus remains unchanged for future invocations offoo
.Just change the function to be:
The shortest answer would probably be "definition is execution", therefore the whole argument makes no strict sense. As a more contrived example, you may cite this:
Hopefully it's enough to show that not executing the default argument expressions at the execution time of the
def
statement isn't easy or doesn't make sense, or both.I agree it's a gotcha when you try to use default constructors, though.
When we do this:
... we assign the argument
a
to an unnamed list, if the caller does not pass the value of a.To make things simpler for this discussion, let's temporarily give the unnamed list a name. How about
pavlo
?At any time, if the caller doesn't tell us what
a
is, we reusepavlo
.If
pavlo
is mutable (modifiable), andfoo
ends up modifying it, an effect we notice the next timefoo
is called without specifyinga
.So this is what you see (Remember,
pavlo
is initialized to []):Now,
pavlo
is [5].Calling
foo()
again modifiespavlo
again:Specifying
a
when callingfoo()
ensurespavlo
is not touched.So,
pavlo
is still[5, 5]
.It may be true that:
it is entirely consistent to hold to both of the features above and still make another point:
The other answers, or at least some of them either make points 1 and 2 but not 3, or make point 3 and downplay points 1 and 2. But all three are true.
It may be true that switching horses in midstream here would be asking for significant breakage, and that there could be more problems created by changing Python to intuitively handle Stefano's opening snippet. And it may be true that someone who knew Python internals well could explain a minefield of consequences. However,
The existing behavior is not Pythonic, and Python is successful because very little about the language violates the principle of least astonishment anywhere near this badly. It is a real problem, whether or not it would be wise to uproot it. It is a design flaw. If you understand the language much better by trying to trace out the behavior, I can say that C++ does all of this and more; you learn a lot by navigating, for instance, subtle pointer errors. But this is not Pythonic: people who care about Python enough to persevere in the face of this behavior are people who are drawn to the language because Python has far fewer surprises than other language. Dabblers and the curious become Pythonistas when they are astonished at how little time it takes to get something working--not because of a design fl--I mean, hidden logic puzzle--that cuts against the intuitions of programmers who are drawn to Python because it Just Works.
This actually has nothing to do with default values, other than that it often comes up as an unexpected behaviour when you write functions with mutable default values.
No default values in sight in this code, but you get exactly the same problem.
The problem is that
foo
is modifying a mutable variable passed in from the caller, when the caller doesn't expect this. Code like this would be fine if the function was called something likeappend_5
; then the caller would be calling the function in order to modify the value they pass in, and the behaviour would be expected. But such a function would be very unlikely to take a default argument, and probably wouldn't return the list (since the caller already has a reference to that list; the one it just passed in).Your original
foo
, with a default argument, shouldn't be modifyinga
whether it was explicitly passed in or got the default value. Your code should leave mutable arguments alone unless it is clear from the context/name/documentation that the arguments are supposed to be modified. Using mutable values passed in as arguments as local temporaries is an extremely bad idea, whether we're in Python or not and whether there are default arguments involved or not.If you need to destructively manipulate a local temporary in the course of computing something, and you need to start your manipulation from an argument value, you need to make a copy.