Try to solve these three problems, and then check the answers at the end of the article.
Tip : Problems have something in common, so brush up on the first problem when you move on to the second or third, it will be easier for you.
First task
There are several variables:
x = 1
y = 2
l = [x, y]
x += 5
a = [1]
b = [2]
s = [a, b]
a.append(5)
What will be displayed when printing
l
and s
?
Second task
Define a simple function:
def f(x, s=set()):
s.add(x)
print(s)
What happens if you call:
>>f(7)
>>f(6, {4, 5})
>>f(2)
Third task
Let's define two simple functions:
def f():
l = [1]
def inner(x):
l.append(x)
return l
return inner
def g():
y = 1
def inner(x):
y += x
return y
return inner
What do we get after executing these commands?
>>f_inner = f()
>>print(f_inner(2))
>>g_inner = g()
>>print(g_inner(2))
How confident are you in your answers? Let's check your case.
Solution of the first problem
>>print(l)
[1, 2]
>>print(s)
[[1, 5], [2]]
Why does the second list react to a change to its first item
a.append(5)
, while the first list completely ignores the same change x+=5
?
Solution of the second problem
Let's see what happens:
>>f(7)
{7}
>>f(6, {4, 5})
{4, 5, 6}
>>f(2)
{2, 7}
Wait, shouldn't the last result be
{2}
?
The solution to the third problem
The result will be like this:
>>f_inner = f()
>>print(f_inner(2))
[1, 2]
>>g_inner = g()
>>print(g_inner(2))
UnboundLocalError: local variable ‘y’ referenced before assignment
Why
g_inner(2)
didn’t she betray 3
? Why f()
does the inner function remember the outer scope, but the inner function g()
does not? They are almost identical!
Explanation
What if I told you that all of these odd behaviors have to do with the difference between mutable and immutable objects in Python?
Modifiable objects, such as lists, sets, or dictionaries, can be modified locally. Immutable objects, such as numeric and string values, tuples, cannot be changed; their "change" will lead to the creation of new objects.
First task explanation
x = 1
y = 2
l = [x, y]
x += 5
a = [1]
b = [2]
s = [a, b]
a.append(5)
>>print(l)
[1, 2]
>>print(s)
[[1, 5], [2]]
Since it is
x
immutable, the operation x+=5
does not change the original object, but creates a new one. But the first item in the list still refers to the original object, so its value doesn't change.
Because a mutable object, then the command
a.append(5)
modifies the original object (rather than creating a new one), and the list s
"sees" the changes.
Explanation of the second task
def f(x, s=set()):
s.add(x)
print(s)
>>f(7)
{7}
>>f(6, {4, 5})
{4, 5, 6}
>>f(2)
{2, 7}
With the first two results, everything is clear: the first value is
7
added to the initially empty set and it turns out {7}
; then the value is 6
added to the set {4, 5}
and obtained {4, 5, 6}
.
And then the oddities begin. The value
2
is not added to the empty set, but to {7}. Why? The initial value of the optional parameter is s
calculated only once: on the first call, s will be initialized as an empty set. And since it is mutable, f(7)
it will be changed in place after being called . The second call f(6, {4, 5})
will not affect the default parameter: the set replaces it {4, 5}
, that is, it is {4, 5}
a different variable. The third call f(2)
uses the same variables
that was used during the first call, but it is not reinitialized as an empty set, but instead its previous value is taken {7}
.
Therefore, you should not use mutable arguments as default arguments. In this case, the function needs to be changed:
def f(x, s=None):
if s is None:
s = set()
s.add(x)
print(s)
Explanation of the third task
def f():
l = [1]
def inner(x):
l.append(x)
return l
return inner
def g():
y = 1
def inner(x):
y += x
return y
return inner
>>f_inner = f()
>>print(f_inner(2))
[1, 2]
>>g_inner = g()
>>print(g_inner(2))
UnboundLocalError: local variable ‘y’ referenced before assignment
Here we are dealing with closures: internal functions remember how their external namespaces looked at the time of their definition. Or at least they should remember, but the second function makes the poker face and behaves as if it hadn't heard of its external namespace.
Why is this happening? When we execute
l.append(x)
, the mutable object created when the function is defined changes. But the variable l
still refers to the old address in memory. However, trying to change an immutable variable in the second function y += x
results in y starting to refer to a different memory address: the original y will be forgotten, which will result in an UnboundLocalError.
Conclusion
The difference between mutable and immutable objects in Python is very important. Avoid the strange behavior described in this article. Especially:
- .
- - .