class Foo:
def __init__(self, x):
self.x = x
def inner1(f):
= 0
f.x
def inner2(f):
= Foo(99)
f
= Foo(1)
f print(f.x, "\n")
inner1(f)print(f.x, "\n")
inner2(f)print(f.x, "\n")
1
0
0
January 21, 2025
I remember this question when I first started to learn Python. It was not very clear to me as at the time I found answer that support both side. After a few years, I got my answer when I was learning more about the Golang. The conclusion is clear as explained in Chapter 6, Learning go.
… the following scenarios are true in Java, Python, JavaScript, and Ruby: - If you pass an instance of a class to a function and you change the value of a field, the change is reflected in the variable that was passed in. - If you reassign the parameter, the change is not reflected in the variable that was passed in. - If you pass nil/null/None for a parameter value, setting the parameter itself to a new value doesn’t modify the variable in the calling function. Some people explain this behavior by saying that class instances are passed by reference in these languages. This is untrue. If they were being passed by reference, scenarios two and three would change the variable in the calling function. These languages are always pass-by-value, just as in Go.
A simplified Python example to illustrate this behavior
class Foo:
def __init__(self, x):
self.x = x
def inner1(f):
f.x = 0
def inner2(f):
f = Foo(99)
f = Foo(1)
print(f.x, "\n")
inner1(f)
print(f.x, "\n")
inner2(f)
print(f.x, "\n")
1
0
0
At first, the instance f
store a value x=1
, this get mutated to 0
by inner1
. But then the inner2
did not change the value and f.x
remains to 0
at the end.
Another common example could be demonstrated with Python dictionary
def mutate(d):
d["a"] = 99
return d
def unchange(d):
d = {"a": 999}
return d
# Original dictionary is updated
dictionary = {"a": 1}
mutate(dictionary)
print(dictionary)
# Dictionary is not updated
dictionary = {"a": 1}
unchange(dictionary)
print(dictionary)
{'a': 99}
{'a': 1}
Why is this happening? It seems that sometimes a function can change the input, but sometime it copies an input instead.
What you are seeing is that every instance of a class in these languages is implemented as a pointer. When a class instance is passed to a function or method, the value being copied is the pointer to the instance.
This is probably why it is confusing to someone who started with Python, as pointer is a distant concept to Python User. I am not going into details to explain what a Pointer is, the intuition is that a pointer is an address that points to the actual data which exist in physical memory. So when you pass an object to a function (technically, everything is an object in Python), you are passing the address of an object. When you update an attribute of the object, the address is still pointing to a correct address, thus it mutates the original input even outside of the scope of the function. In contrast, if you are creating a new dictionary (even though it has the same variable name), it is a new address that does not affect the original variable.