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.

4
  • 1
    Related: Why do we use init in Python classes?, What init and self do on Python?, Commented Jul 25, 2020 at 23:48
  • 1
    docs.python.org/3/tutorial/classes.html, Commented Jul 25, 2020 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. Commented Jul 25, 2020 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. Commented Jul 26, 2020 at 1:32

2 Answers 2

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
Sign up to request clarification or add additional context in comments.

3 Comments

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!
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 ?
@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
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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.