Search code examples
pythonfunctionglobal-variableslocal-variables

Why my local variable in a function affect global variable without using 'global'?


I am still learning about python. I am stuck with this local variable behavior. I am trying to make a simple coffee machine. this is the part of code.

MENU = {
    "espresso": {
        "ingredients": {
            "water": 50,
            "coffee": 18,
        },
        "cost": 1.5,
    },
    "latte": {
        "ingredients": {
            "water": 200,
            "milk": 150,
            "coffee": 24,
        },
        "cost": 2.5,
    },
    "cappuccino": {
        "ingredients": {
            "water": 250,
            "milk": 100,
            "coffee": 24,
        },
        "cost": 3.0,
    }
}

resources = {
    "water": 300,
    "milk": 200,
    "coffee": 100,
}

def calculate(current, ingredients):
    remaining = current
    print(remaining)
    print(ingredients)
    for i in ingredients :
        remaining[i] = remaining[i] - ingredients[i]
        if remaining[i] < 0 :
            print(f"sorry, there is not enough {i}")
            print(remaining)
            return resources
    return remaining

user_input = input("What would you like? (espresso/latte/cappuccino): ").lower()
coffee = MENU[user_input]
ingredients = coffee["ingredients"]
cost = coffee["cost"]
# check if coffee can be made.
remaining = calculate(resources, ingredients)
print(resources)
print(remaining) 

When I run it, I expect that the global variable of resources won't change. If I choose latte, the output should be like this:

{'water': 300, 'milk': 200, 'coffee': 100}
{'water': 50, 'milk': 100, 'coffee': 76}

However, the printed resources change its value. I noticed using thonny debugger that when the calculation occurs, both local and global change their value. I didn't use global command. Can anyone explain why?


Solution

  • resources is a mutable object. When it is passed to calculate, current becomes another name for that object.

    remaining = current assigns another name to the object, and remaining[i] = remaining[i] - ingredients[i] mutates that object.

    To fix, use:

    remaining = current.copy()
    

    This will make a new object that modifying won’t affect the original.

    Note that if the original object itself contains mutable objects, then copy.deepcopy() may be needed, but not in this case.