Chris Jaurigue
Chris Jaurigue

Reputation: 17

Using __init__ in OOP

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.

Upvotes: 0

Views: 226

Answers (2)

DeepSpace
DeepSpace

Reputation: 81684

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

Upvotes: 1

NotAName
NotAName

Reputation: 4347

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.

Upvotes: 0

Related Questions