0

I would like to add new ingredients to a list which is part of the Pizza object. This object is saved as the value of a key-value dictionary in pizzas (See code below).

class Pizza:
    def __init__(self, ingredients = []):
        self.ingredients = ingredients


def main():
    pizzas = {}
    availablePizzas = ["Margherita", "Marinara", "Diavola"]

    for pizza in availablePizzas:
        pizzas.update({pizza: Pizza()})

    pizza = pizzas.get("Margherita")
    pizza.ingredients.append("Mozzarella")
    pizzas.update({"Margherita": pizza})

    printPizzas(pizzas)


def printPizzas(pizzas):
    for pizza, value in pizzas.items():
        print(pizza, value.ingredients)


main()

When I run this code it will append the new ingredient Mozzarella to each pizza in the dictionary pizzas. The result:

Margherita ['Mozzarella']
Marinara ['Mozzarella']
Diavola ['Mozzarella']

How can I change this so that the ingredient Mozzarella will be only added to the key ingredient Margherita? Requested result:

Margherita ['Mozzarella']
Marinara []
Diavola []

2 Answers2

1

The problem comes from your class definition :

class Pizza:
    def __init__(self, ingredients = []):
        self.ingredients = ingredients

You're using a mutable reference as an argument for the default value ofyour ingredients. This reference (thus the same list) is initialized the first time your class definition is read and is shared for every instance of Pizza using this default argument, hence why every pizza's ingredients are modified when you append an element to only one of them.

You should instead declare the default value inside the body of the method :

class Pizza:
    def __init__(self, ingredients = None):
        if ingredients is None:
            self.ingredients = []
        else:
            self.ingredients = ingredients
        
        # Using ternary :
        # self.ingredients = ingredients if ingredients is not None else []

You can have more information here: https://docs.python-guide.org/writing/gotchas/

qcoumes
  • 505
  • 4
  • 13
  • Nice one! You can replace if-else clause with or: `self.ingredients = ingredients or []` – koPytok Mar 05 '21 at 14:29
  • Yes, as long as you're using iterable and not any other value that can be False (which seems to be the case). – qcoumes Mar 05 '21 at 14:31
0

This is a fun one. This happens because of how mutable default arguments work in Python. Ideally you should go read up on that eg here:

tl;dr is that python functions create a default argument only once, so when you do: def __init__(self, ingredients = []): only a single list is created and ALL Pizza().ingredients will point to this single list. You can avoid that with something like the answer here: What is the pythonic way to avoid default parameters that are empty lists?

P.S:

    pizza = pizzas.get("Margherita")
    pizza.ingredients.append("Mozzarella")
    pizzas.update({"Margherita": pizza})

Here the pizzas.update is does nothing. "Margherita" is already pointing at and a Pizza you have stored in the variable pizza. So while the data in the Pizza changed, the dictionary is still pointing at the same object.

Faboor
  • 1,365
  • 2
  • 10
  • 23
  • 1
    You probably should have voted to close as a duplicate rather than provide an answer referencing (canonical) SO Q&A's on the topic. The offsite links could have been put in a comment. – wwii Mar 05 '21 at 14:41