Reputation: 52
A dictionary that I pass as an argument seems to behave like a global variable. This was a surprise to me but it seems that it makes sense in Python (see for instance this topic). However, I'm very surprised that I get a different behavior for a different type of variable. Let assume that I have a script main.py that calls two functions from a module.
import mymodule
an_int = 42
a_dict = {'value':42}
mymodule.print_and_reassign(a_dict, an_int)
mymodule.print_again(a_dict, an_int)
with my modules.py containing the following functions
def print_and_reassign(a_dict, an_int):
# Dictionary
print(f"Dictionary value: {a_dict['value']}")
a_dict['value']=970
# Integer
print(f"Integer value: {an_int}")
an_int=970
def print_again(a_dict, an_int):
# Dictionary
print(f"Dictionary value: {a_dict['value']}")
# Integer
print(f"Integer value: {an_int}")
By running main.py, I get the following results
Dictionnary value: 42
Integer value: 42
Dictionnary value: 970
Integer value: 42
This is very counterintuitive to me. Why does the dictionary changed while the integer remains unchanged?
Upvotes: 1
Views: 1612
Reputation: 5264
Calling a_dict['value'] = 970
underneath manipulates a_dict
by calling its __setitem__
method, therefore a_dict
's contents change.
Calling an_int = 970
assigns a new value to a local an_int
variable, shadowing the value passed as a parameter.
Upvotes: 2
Reputation: 1858
Yes, that's a quite confusing part of programming in Python (or, rather, programming in general).
What happens is essentially that what you have in the beginning is this:
When calling the function, the variables inside the function's scope are actually something different than the outer ones, even though you named them the same and they initially have the same value:
Then you change them, and this happens:
And after the function returns and its scope is destroyed, you are left with this:
Upvotes: 3
Reputation: 25489
As @jonsharpe said above,
Dictionaries are mutable, integers aren't. And assigning a new value to the local variable is fundamentally different to calling a method on the value it refers to
To reinforce jon's point, try reassigning the variable a_dict
inside the function like so: a_dict = {"value": 970}
.
def print_and_reassign(a_dict, an_int):
print(f"Dictionary value before: {a_dict['value']}")
a_dict = {"value": 970}
print(f"Dictionary value after: {a_dict['value']}")
print(f"Integer value before: {an_int}")
an_int = 970
print(f"Integer value after: {an_int}")
def print_again(a_dict, an_int):
# Dictionary
print(f"Dictionary value outside: {a_dict['value']}")
# Integer
print(f"Integer value outside: {an_int}")
Now, when you run your code you see that a_dict
is not modified outside the function.
Dictionary value before: 42
Dictionary value after: 970
Integer value before: 42
Integer value after: 970
Dictionary value outside: 42
Integer value outside: 42
When you assign a new value to the name a_dict
inside the function, it isn't reflected outside the function. This is what happens when you do an_int = 970
. Since integers are immutable, this is also what would happen if you did something like an_int += 100
.
When you do a_dict['value'] = 970
, you modify the value that is referred to by a_dict
. Since dicts are mutable, this modification doesn't need a reassignment so it shows in every variable refers to this value.
Upvotes: 1