This is a slightly tricky case. It makes sense when you have a good understanding of how Python treats names and objects. You should strive to develop this understanding as soon as possible if you're learning Python, because it is central to absolutely everything you do in Python.
Names in Python are things like a
, f1
, b
. They exist only within certain scopes (i.e. you can't use b
outside the function that uses it). At runtime a name refers to a value, but can at any time be rebound to a new value with assignment statements like:
a = 5
b = a
a = 7
Values are created at some point in your program, and can be referred to by names, but also by slots in lists or other data structures. In the above the name a
is bound to the value 5, and later rebound to the value 7. This has no effect on the value 5, which is always the value 5 no matter how many names are currently bound to it.
The assignment to b
on the other hand, makes binds the name b
to the value referred to by a
at that point in time. Rebinding the name a
afterwards has no effect on the value 5, and so has no effect on the name b
which is also bound to the value 5.
Assignment always works this way in Python. It never has any effect on values. (Except that some objects contain "names"; rebinding those names obviously effects the object containing the name, but it doesn't affect the values the name referred to before or after the change)
Whenever you see a name on the left side of an assignment statement, you're (re)binding the name. Whenever you see a name in any other context, you're retrieving the (current) value referred to by that name.
With that out of the way, we can see what's going on in your example.
When Python executes a function definition, it evaluates the expressions used for default arguments and remembers them somewhere sneaky off to the side. After this:
def f1(a, l=[]):
l.append(a)
return(l)
l
is not anything, because l
is only a name within the scope of the function f1
, and we're not inside that function. However, the value []
is stored away somewhere.
When Python execution transfers into a call to f1
, it binds all the argument names (a
and l
) to appropriate values - either the values passed in by the caller, or the default values created when the function was defined. So when Python beings executing the call f3(5)
, the name a
will be bound to the value 5 and the name l
will be bound to our default list.
When Python executes l.append(a)
, there's no assignment in sight, so we're referring to the current values of l
and a
. So if this is to have any effect on l
at all, it can only do so by modifying the value that l
refers to, and indeed it does. The append
method of a list modifies the list by adding an item to the end. So after this our list value, which is still the same value stored to be the default argument of f1
, has now had 5 (the current value of a
) appended to it, and looks like [5]
.
Then we return l
. But we've modified the default list, so it will affect any future calls. But also, we've returned the default list, so any other modifications to the value we returned will affect any future calls!
Now, consider f2
:
def f2(a, b=1):
b = b + 1
return(a+b)
Here, as before, the value 1 is squirreled away somewhere to serve as the default value for b
, and when we begin executing f2(5)
call the name a
will become bound to the argument 5, and the name b
will become bound to the default value 1
.
But then we execute the assignment statement. b
appears on the left side of the assignment statement, so we're rebinding the name b
. First Python works out b + 1
, which is 6, then binds b
to that value. Now b
is bound to the value 6. But the default value for the function hasn't been affected: 1 is still 1!
Hopefully that's cleared things up. You really need to be able to think in terms of names which refer to values and can be rebound to point to different values, in order to understand Python.
It's probably also worth pointing out a tricky case. The rule I gave above (about assignment always binding names with no effect on the value, so if anything else affects a name it must do it by altering the value) are true of standard assignment, but not always of the "augmented" assignment operators like +=
, -=
and *=
.
What these do unfortunately depends on what you use them on. In:
x += y
this normally behaves like:
x = x + y
i.e. it calculates a new value with and rebinds x
to that value, with no effect on the old value. But if x
is a list, then it actually modifies the value that x
refers to! So be careful of that case.
bomb = [1, 2, 3]; print(f1(4, bomb)); print(f1(5, bomb))
? – Undertake