Antoine T
Antoine T

Reputation: 52

Dictionary behaves like a global variable

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

Answers (3)

ljmc
ljmc

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

Jan Pokorný
Jan Pokorný

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:

enter image description here

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:

enter image description here

Then you change them, and this happens:

enter image description here

And after the function returns and its scope is destroyed, you are left with this:

enter image description here

Upvotes: 3

pho
pho

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

Related Questions