0

I am learning to develop code using OOP. However I am having issues understanding when to use the __init__ constructor. Is __init__ mandatory in OOP? If so how would I use __init__ ?

What the following code does is takes the users requested pizza size and toppings and returns and final total.

When I run the following code:

class Pizza:
    """ customer orders pizza size and pizza toppings"""

    def size_menu(self): # Provides user a menu
        
        self.menu_s = """
        What size pizza would you like?
            _____________________________________________________________
            | 1: Small  |  2: Large  |  3: Extra Large  |  4: Party Size |
            |  $6.23    |   $10.23   |      $12.23      |      $24.23    |
            |___________|____________|__________________|________________|
            """
        print(self.menu_s)
        return self.menu_s

    def size_order(self): # Gets size wanted and returns pizza total. 
        size_mappings = {
            1: "Small",
            2: "Large",
            3: "Extra Large",
            4: "Party Size"
            }

        cost_mappings = {
            "Small": 6.23,
            "Large": 10.23,
            "Extra Large": 12.23,
            "Party Size": 24.23
            }

        response = input('-') # user inters 1-4 for pizza size wanted and returns a size total.
        self.size_wanted = float(response) # Turns response as a float
        self.size_wanted = size_mappings[self.size_wanted] # Size requested
        self.size_cost = cost_mappings[self.size_wanted] # Cost of size

        print(f"Getting your {self.size_wanted} pizza ready.")
        print(f"Your current total is: ${self.size_cost}")
        return self.size_cost

    def topping_menu(self): # Provides user with toppings menu
        self.menu_t = """
        What toppings do you want on your pizza?
        _____________________________________________________
       |   1:Bacon      |  4:Anchovies     |  7:Black Olives |
       |   2:Pepperoni  |  5:Spinach       |  8:Chicken      |
       |   3:Mushrooms  |  6:Onions        |  9:Ground Beef  |
       |________________|__________________|_________________| 
       What toppings do you want on your pizza?
       """
        print(self.menu_t)
        return self.menu_t
       

    def topping_order(self): # Gets toppings the user wants and returns a total of all toppings. 
        topping_mappings = {
            1: 'Bacon', 
            2: 'Pepperoni', 
            3: 'Mushrooms', 
            4: 'Anchovies', 
            5: 'Spinach', 
            6: 'Onions', 
            7: 'Black Olives',
            8: 'Chicken', 
            9: 'Ground Beef'
            }

        self.requested_toppings = []

        while True:
            response = input('-')

            if response == 'q':
                break

            toppings_wanted = response
            toppings_wanted = topping_mappings[int(toppings_wanted)]
            self.requested_toppings.append(toppings_wanted)

            if toppings_wanted in topping_mappings.values():
                print(f"Adding: {toppings_wanted}")

            else:
                print(f"We do not have {toppings_wanted}")

        self.topping_total = len(self.requested_toppings) * float(1.23)

        print("\nWe are adding the requested toppings to your pizza.")
        print(f"your topping total will be: ${self.topping_total}")
        return self.topping_total

   
    def final_total(self):
        total = self.size_cost + self.topping_total
        total = float(total)
        print(f"\nYour final order total will be ${total}")



if __name__ == '__main__':
    
    customer_order = Pizza()
    customer_order.size_menu()
    customer_order.size_order()
    customer_order.topping_menu()
    customer_order.topping_order()
    customer_order.final_total()
    
    

I am wondering why would I use the __init__ constructor if the program is returning the information I am seeking? Thank you for the assistance.

  • 1
    Related: [Why do we use __init__ in Python classes?](https://stackoverflow.com/questions/8609153/why-do-we-use-init-in-python-classes), [What __init__ and self do on Python?](https://stackoverflow.com/questions/625083/what-init-and-self-do-on-python), – wwii Jul 25 '20 at 23:48
  • 1
    https://docs.python.org/3/tutorial/classes.html, – wwii Jul 25 '20 at 23:51
  • What advantage do you get from writing those functions as a class? You could have written those functions in a module, imported the module and achieved a similar if not the same effect - some would say this would be thr right way to do it in Python. – wwii Jul 25 '20 at 23:55
  • @wwii Thanks for the resources. I've read the Python documentation, however, it was still hard to understand. I know I could have just written them into a module however I saw this code as a great opportunity to problem solve and focus on OOP. – Chris Jaurigue Jul 26 '20 at 01:32

2 Answers2

1

While this code works, it is not very scalable nor reusable.

What if tomorrow you will want to allow ordering Pizza with input from a json file rather than user input?

What if you forget to call one of the order methods? The call to final_total will crash your program since some attributes will be missing.

Also, it is considered an anti-pattern to create attributes outside of the __init__ method because it makes the code unreadable, hard to follow and hard to use (at the moment, not all Pizza instances will have the same attributes at all times).

How to make it better

  1. Move all the hard-coded, permanent values to be a class attributes. These will be shared among all instances of Pizza.

  2. Get all the arguments Pizza needs in order to be a Pizza in __init__. These will be unique for each Pizza.

  3. Implements possible methods of ordering Pizza. One of them might be from_user_input.

Note that this code might use a little bit more advanced concepts of Python than you might be aware of at the moment. Use this as an opportunity to learn. It is far from perfect (for example, it is missing some very basic error checking and handling), but it is a good place to start.

class Pizza:
    size_mappings = {
        1: "Small",
        2: "Large",
        3: "Extra Large",
        4: "Party Size"
    }
    cost_mappings = {
        "Small": 6.23,
        "Large": 10.23,
        "Extra Large": 12.23,
        "Party Size": 24.23
    }
    cost_per_topping = 1.23
    topping_mappings = {
        1: 'Bacon',
        2: 'Pepperoni',
        3: 'Mushrooms',
        4: 'Anchovies',
        5: 'Spinach',
        6: 'Onions',
        7: 'Black Olives',
        8: 'Chicken',
        9: 'Ground Beef'
    }
    size_menu = """
        What size pizza would you like?
            
            1: Small         ${Small}
            2: Large         ${Large}
            3: Extra Large   ${Extra Large} 
            4: Party Size    ${Party Size}\n\n"""

    def __init__(self, size_wanted, requested_toppings):
        self.size_wanted = size_wanted
        self.requested_toppings = requested_toppings

    def finalize_order(self):
        cost = self.cost_mappings[self.size_mappings[self.size_wanted]] + len(self.requested_toppings) * self.cost_per_topping
        print("Thanks for ordering. The final cost is {}".format(cost))

    @classmethod
    def show_size_menu(cls):
        return cls.size_menu.format(**cls.cost_mappings)

    @classmethod
    def show_toppings_menu(cls):
        return "What toppings would you want on your Pizza?\n{}".format(
            '\n'.join('{}: {}'.format(k, v) for k, v in cls.topping_mappings.items())
        )

    @classmethod
    def from_user_input(cls):
        size_wanted = int(input(cls.show_size_menu()))
        requested_toppings = []
        print(cls.show_toppings_menu())
        print("Type requested toppings' numbers, and 'q' when done")
        while True:
            req_topping = input()
            if req_topping == 'q':
                break
            try:
                requested_toppings.append(int(req_topping))
            except ValueError:
                print('Only numbers or q')
        return cls(size_wanted, requested_toppings)


p = Pizza.from_user_input()
p.finalize_order()

Benefits:

  1. All the constant values are at one location, right under class Pizza. If we ever need to change something we know exactly where it is.

  2. The Pizza class is decoupled from the creation method and does not rely on us calling 5 methods in the correct order every time we want to create an instance.

  3. If tomorrow someone will ask us to create a Pizza from a json file, it is just a matter of implementing def from_json_file.

Example execution of above code:

What size pizza would you like?
            
            1: Small         $6.23
            2: Large         $10.23
            3: Extra Large   $12.23 
            4: Party Size    $24.23

2
What toppings would you want on your Pizza?
1: Bacon
2: Pepperoni
3: Mushrooms
4: Anchovies
5: Spinach
6: Onions
7: Black Olives
8: Chicken
9: Ground Beef
Type requested toppings' numbers, and 'q' when done
1
3
7
q
Thanks for ordering. The final cost is 13.92
DeepSpace
  • 78,697
  • 11
  • 109
  • 154
  • Wow, thank you so much for taking the time to reformat everything and provide your insight. Your explanation helps me understand __init__ a bit more, I just have to spend sometime working with it. Also thanks for adding in some unfamiliar concepts gives me something to work on. Thank you again! – Chris Jaurigue Jul 26 '20 at 01:29
  • As I started looking at your example you provided, I noticed a couple of things I have questions on. Is there a reason you are using .format() instead of an 'f string' or is it just personal preference? Also you use 'cls' is that just to distinguish between different functions ? – Chris Jaurigue Jul 26 '20 at 03:15
  • @ChrisJaurigue 1. I tend to use `.format` on stackoverflow because f-strings were only introduced in Python 3.6, so using `.format` will work for everyone regardless of the Python version they are using. 2. I'm using `cls` to refer to the class itself in class methods. The first argument for class methods (marked with `@classmethod`) is the class itself (in this case, `Pizza`), just like the first argument for "normal" (instance) methods is the instance itself (`self`). Using the name `cls` is a convnetion, just like using the name `self` is a convention – DeepSpace Jul 26 '20 at 10:04
0

In this code you don't really need to have __init__. You need it when you have a class that from which you expect to create multiple instances with different parameters. Say, instead of Pizza, you had a class PizzaRestaurant, but different restaurants can have different selections of pizza and different prices then you could do something like:

class PizzaRestaurant:
    def __init__(self, menu: dict) -> None:
        self.menu = menu

So now you can initialise multiple instances of PizzaRestaurant where each restaurant can have a different menu supplied to the constructor in the form of a dict with pizza types and prices for 3 different sizes:

johns_menu = {'hawaiian': [10, 12, 15], 'capriciosa' : [11, 13, 16], 'margherita' : [9, 10, 12]}

johns_pizzeria = PizzaRestaurant(johns_menu)

Here dictionary johns_menu will be passed to the __init__ constructor that will initialise class attributes and then you can calculate order totals with this specific set of pizza types and prices.

But then you can also create another instance called, say, Luigi's pizzeria which will have different selection and different prices in which case you simply supply a different dict when you call the class.

If a class simply does some calculations and returns results: a) you don't need __init__; b) It doesn't specifically need to be a class - it can work just as well as a function.

NotAName
  • 3,821
  • 2
  • 29
  • 44