Reputation: 17
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
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
Move all the hard-coded, permanent values to be a class attributes. These will be shared among all instances of Pizza
.
Get all the arguments Pizza
needs in order to be a Pizza
in __init__
. These will be unique for each Pizza
.
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:
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.
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.
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
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