Hello Pythonistas, I hope you all are doing well. Today we’ll learn managing multiple instances π€ΉββοΈ in Python OOP.
This is your Restaurant class’s code till now:-
class Restaurant:
menu = {"Pizza": 10, "Waffle": 4, "Coffee": 2.5}
def __init__(self,name):
self.name = name #string
self.order = [] #A list
self.total_bill = 0 #A float value
@classmethod
def show_menu(cls):
print("Here's the menu of our restaurant:-")
for item, price in cls.menu.items():
print(f"{item} ${price}")
def place_order(self,order):
print(f"We are Placing Order please wait {self.name}...")
self.order = order.title().split(", ")
print(f"Your is Order Placed successfully {self.name}!")
print(f"Your order is: \n{self.order}")
def pay_bill(self):
if len(self.order)!=0:
for i in self.order:
if i in Restaurant.menu:
self.total_bill+=Restaurant.menu[i]
else:
print(f"Sorry {self.name}, {i} is not available in this restaurant.")
else:
print("You need to Place order first!")
print(f"Your total bill amounts to ${self.total_bill}")
@staticmethod
def welcome():
print("Hello, welcome to the restaurant, how many people will be dining today?")
Your restaurant class functions wellπ. That’s right.
But is that how a restaurant functions?π€
I don’t think so. A restaurant has a lot more than this cookingπ¨βπ³, parkingπ, seating areaπͺ, marketingπ‘, financingπ΅, etc.
Is our class anyways doing it? Then, Restaurant isn’t the rightπ name for our class.
We can call it a botπ€ for managing the online order system of our restaurant. Like domino’s dom bot.
Sounds good?
Then let’s name it something.
Groot!
Yes, the one from the avengers.π
Ok, so now our botπ€ has got a good name. But, what will happen when our bot gets more than 2-3 customers?
It won’t function well.π΅
Then what’s the use of making it in OOP?
Well, That’s why I am up with this post. You know very well how to make a class. Now we will see how to use a class.
Sounds interestingπ§? Let’s get started.
Contents
Before divingπββοΈ into managing multiple instances, here’s the solution to the previous post’s challenge.
Previous post’s challenge’s solution
from booksAndLogo import python_books #I stored books in a separate file
class Library:
def __init__(self, list, name):
self.booksList = list #list of books available in the library
self.name = name #name of the library
self.issueDict = {} #to store the books issued along with the issuer
self.want_membership = input("If you want to be a member enter y else enter n: ").lower()
if self.want_membership == 'y':
self.memeber = self.Membership(self.name,input("Enter your membership no."))
def list_books(self):
print(f"\nThe following books are available in {self.name} library.")
for book in self.booksList:
print(book)
print()
def issue_books(self, issuer, book):
"""inputs the issuer and book.
checks whether taken by someone else(using the issueDict in the init).
If it is not then updates the issueDict.
If it is then says its already taken."""
if book not in self.issueDict.keys(): #keys() funciton provides list of all the keys in the dictionary.
self.issueDict.update({book:issuer})
print("Issue-Book database has been updated. You can take the book now")
else:
print(f"Book is already being used by {self.issueDict[book]}")
def donate_books(self, book_name):
"""inputs book and adds it to the booksList.
And thanks the doner"""
self.booksList.append(book_name)
print("Book has been added to the book list.\nThanks for donating.")
def return_books(self, book):
"""pops the book from the issueDict dictionary."""
if book in self.issueDict.keys():
self.issueDict.pop(book)
print(f"{book} is returned successfully.")
else:
print("Check again. No such books were issued.")
class Membership:
members = {}
def __init__(self,name,mnum):
self.name = name
self.mnum = mnum
# To use the class variable of inner class inside of it we need to use outer class's name.
Library.Membership.members[self.name]=self.mnum
MyLib =Library(python_books,"Python_Lib")
Let’s dive into Managing multiple instances.
Some Required changes in the Groot class
We need to make some changes in our code to learn the concept of managing multiple instances.
adding a method to help us debug
Let’s first add a show method that will help us in debugging.π
def show(self):
print("\n\n**********Customer's details:************")
print(f"Name: {self.name}")
print(f"Order: {self.order}")
print(f"Bill Amount: {self.total_bill}")
Now when you go on using it you’ll noticeπ§ a problem:
cust1 = Groot("John")
cust1.place_order("Pizza, Waffle")
cust1.show()
Output:
We are Placing Order please wait John...
Your is Order Placed successfully John!
Your order is:
['Pizza, Waffle']
**********Customer's details:************
Name: John
Order: ['Pizza', 'Waffle']
Bill Amount: 0
updating the total bill when the order is placed
Here, John has ordered a Pizzaπ and a Waffleπ§ but still, the bill amount shows $0. This is wrong, we need to update our place_order()
method.
def place_order(self,order):
#simply placing order
print(f"We are Placing Order please wait {self.name}...")
self.order = order.title().split(", ")
print(f"Your is Order Placed successfully {self.name}!")
print(f"Your order is: \n{self.order}")
#updating the bill's amount
for i in self.order:
if i in Groot.menu:
self.total_bill+=Groot.menu[i]
#in case the order is out of the menu
else:
print("Please select from the following items:\n")
Groot.show_menu()
It isn’t just enough to change the place_order()
method only, this raisesπ the need to change the pay_bill()
method too.
separating the total bill and the paid bill
For this, we would need an instance variable for the unpaid billπ΅.
def pay_bill(self,amount):#we gave this a new parameter.
# Checking whether the customer has made order
if len(self.order)!=0:
# showing the total amount of bill
print(f"Your bill amount = ${self.total_bill}")
# deducting it from the unpaid bill amount
self.unpaid-=amount
# checking if the bill is fully paid
if self.unpaid==0:
# if it is then bill amount = $0
self.total_bill-=amount
print(f"Thanks for visiting.")
# if it isn't then the customer is informed about the amount yet to be paid.
else:
print(f"You are yet to pay ${self.unpaid}")
else:
print("You need to Place order first!")
We will also need to update self.unpaid
in the
__init__()
– declaring,place_order()
– equating it to the total bill, andshow()
– just to display methods
Now let’s test our code:-
cust1 = Groot("John")
cust1.place_order("Pizza, Waffle")
cust1.pay_bill(13)
cust1.show()
Output:
We are Placing Order please wait John...
Your is Order Placed successfully John!
Your order is:
['Pizza', 'Waffle']
Your bill amount = $14
You are yet to pay $1
Your total bill amounts to $14
**********Customer's details:************
Name: John
Order: ['Pizza', 'Waffle']
Bill Amount: $14
Amount yet to be paid: $1
Works fine. Now let’s manageπ€ΉββοΈ more than 1 customer with our Groot
class.
Managing Multiple Objects: Using a list
One way you can think of doing it is by simply creating the instances as we did above.
But that would be messy and our code will be spaghetti.π
This isn’t why started OOP in the first place.
We can do it by storing the customers(instances not the real humans) in a list.[]
This wouldn’t make up a perfectπ€© solution but it is a good start.π
from restaurant import Groot #I stored it in another file for convenience
cust_list = []
cust1 = Groot("John")
cust_list.append(cust1)
print("\nJohn placing order...")#For our understanding
cust_list[0].place_order("coffee")
cust_list[0].show()
print("\n\n")
name = input("What is your name? ").title()
cust2 = Groot(name)
cust_list.append(cust2)
print(f"{name} placing order...")#For our understanding
order = input("What would you like to have? ")
cust_list[1].place_order(order)
cust_list[1].show()
Output:
John making order...
We are Placing Order please wait John...
Your is Order Placed successfully John!
Your order is:
['Coffee']
**********Customer's details:************
Name: John
Order: ['Coffee']
Bill Amount: $2.5
Amount yet to be paid: $2.5
What is your name? jenny
Jenny placing order...
What would you like to have? waffle
We are Placing Order please wait Jenny...
Your is Order Placed successfully Jenny!
Your order is:
['Waffle']
**********Customer's details:************
Name: Jenny
Order: ['Waffle']
Bill Amount: $4
Amount yet to be paid: $4
Here, the output of the code remains the same.π―ββοΈ
But, now we have a list of customers having their own uniqueβ¨ index numbers. Also, despite changing our code here we didn’t need to change the code of the Groot class.
As our Groot class takes care of the managementπ€΅ of orders we can focus on improving the user experience.
Tell me what if a customer has taken the order successfully would we still need its object?
A big no. ββ
Imagine you had 1,000 customers by the end of the month and 1,000,000 by the end of this year. These are the no. of instances you would have also this is how long the list is going to be.
How many systems do you think you would need to store that amount of data?
This might make you think we will change the class’s code.
But, you can simply do it this way:(say John has received and paid for his order)
cust_list.pop(0)
It’s easy and works but, now Jenny’s customer(index) no. is changed. And this wouldn’t be a good idea when we have a larger no. of customers.
This means that we need unique identifiers for our customer object.
And what can be used for that?
A dictionary!
Managing Multiple Instances: Using a Dictionary
For doing this, we will first need to create a dictionary and a counter to keep on giving unique customer no. to all the customers.
cust_dict = {}
custNo = 0
cust1 = Groot("John")
cust1No = custNo #giving customer no. to John.
cust_dict[cust1No] = cust1 #adding John to dictionary
print(f"\nHey! Your customer no. is {cust1No}") #informing John his customer no.
custNo += 1 #increasing the customer no. counter by 1 in order to make no. unique
print("\nJohn placing an order...")
cust_dict[cust1No].place_order("coffee") #Using method using the dictionary
cust_dict[cust1No].show()
Output:
Your customer no. is 0
John placing an order...
We are Placing Order please wait John...
Your is Order Placed successfully John!
Your order is:
['Coffee']
**********Customer's details:************
Name: John
Order: ['Coffee']
Bill Amount: $2.5
Amount yet to be paid: $2.5
Don’t you think after doing all of these we still have to code a lot to get the work done?
What if we set an endless while loop?
This while loop will:-
- show up a list of actions possible.
- The customer will choose from the list.
- Enter the required details.
- And will get the work done.
This means it would be one step closer to the normal apps.
Let’s start making the while loop.
First, we will set up a counter and print statements so that customer can choose what he/she wants to do.
cust_dict = {}
custNo = 0
while True:
print("Welcome to our restaurant!")
print("1. See the menu.")
print("2. Place order.")
print("3. Pay the bill.")
print("4. Get customer details.")
print("5. Exit.")
#This is to take up customer's choice from the above menu.
choice = int(input("What would you like to do?\n"))
Now, let’s set up an elif ladder that works based on the input given.
#show menu
if choice == 1:
pass
#place order will create an object of Groot class too
elif choice == 2:
pass
#allows to pay bill if order is placed
elif choice == 3:
pass
#allows to get details if order is placed
elif choice == 4:
pass
#allows to exit from the loop(system)
elif choice == 5:
pass
else:
print("Invalid choice!!")
To show the menu we will directly use the show_menu() method on the Groot class:
if choice == 1:
print("\n\n**** See the menu ****")
Groot.show_menu()
print("\n\n")
To place an order we will input the name, create an instance give it a customer no., store it in the dictionary, and will use the place_order() method on the instance. Order will be also taken in the form of input.
elif choice == 2:
print("\n\n**** Place an Order ****")
name = input("What is your good name? ")
cust = Groot(name)
custn = custNo
custNo += 1
cust_dict[custn] = cust
cust_dict[custn].show_menu()
order = input("What would you like to have?(give it in comma-separated form)\n")
cust_dict[custn].place_order(order)
print(f"{name} your customer no. is {custn}\n\n")
To pay the bill we will check whether the customer has made an order or not. If he/she has then, we will accept the payment and will call the pay_bill() method.
elif choice == 3:
print("\n\n**** Pay the bill ****")
custn = int(input("Enter your customer number: "))
if custn in cust_dict.keys():
amt = int(input("Enter the bill amount: "))
cust_dict[custn].pay_bill(amt)
print("\n\n")
else:
print("Place an order first!\n\n")
To get the details we will check whether the customer has made an order or not. If he/she has then, we will show the details.
elif choice == 4:
custn = int(input("Enter your customer number: "))
if custn in cust_dict.keys():
cust_dict[custn].show()
print("\n\n")
else:
print("Place an order first!\n\n")
To exit, we will use the break statement. And if the user has entered an invalid choice then, we will inform the user about it.
elif choice == 5:
print("\n\nThanks for visiting!!\n\n")
break
else:
print("\n\nInvalid choice!!")
For those who messed up the code. Here’s the full code:
from restaurant import Groot
cust_dict = {}
custNo = 0
while True:
print("Welcome to our restaurant!")
print("1. See the menu.")
print("2. Place order.")
print("3. Pay the bill.")
print("4. Get customer details.")
print("5. Exit.")
choice = int(input("What would you like to do?\n"))
if choice == 1:
print("\n\n**** See the menu ****")
Groot.show_menu()
print("\n\n")
elif choice == 2:
print("\n\n**** Place an Order ****")
name = input("What is your good name? ")
cust = Groot(name)
custn = custNo
custNo += 1
cust_dict[custn] = cust
cust_dict[custn].show_menu()
order = input("What would you like to have?(give it in comma-separated form)\n")
cust_dict[custn].place_order(order)
print(f"{name} your customer no. is {custn}\n\n")
elif choice == 3:
print("\n\n**** Pay the bill ****")
custn = int(input("Enter your customer number: "))
if custn in cust_dict.keys():
amt = int(input("Enter the bill amount: "))
cust_dict[custn].pay_bill(amt)
print("\n\n")
else:
print("Place an order first!\n\n")
elif choice == 4:
custn = int(input("Enter your customer number: "))
if custn in cust_dict.keys():
cust_dict[custn].show()
print("\n\n")
else:
print("Place an order first!\n\n")
elif choice == 5:
print("\n\nThanks for visiting!!\n\n")
break
else:
print("\n\nInvalid choice!!")
Managing Multiple Objects: Creating Object Manager Object
Now, you can make it, even more, easier to access and debug. For this, you need to give all the above facilities in another class. Then, you just need to access all these methods from this class only. The main code will be super small.
Here’s how:
from restaurant import Groot
class Restaurant:
def __init__(self):
self.cust_dict = {}
self.custNo = 0
def showMenu(self):
print("\n\n**** See the menu ****")
Groot.show_menu()
print("\n\n")
def placeOrder(self):
print("\n\n**** Place an Order ****")
name = input("What is your good name? ")
cust = Groot(name)
custn = self.custNo
self.custNo += 1
self.cust_dict[custn] = cust
print(f"{name} your customer no. is {custn}\n\n")
self.cust_dict[custn].show_menu()
order = input("What would you like to have?(give it in comma-separated form)\n")
self.cust_dict[custn].place_order(order)
def payBill(self):
print("\n\n**** Pay the bill ****")
custn = int(input("Enter your customer number: "))
if custn in self.cust_dict.keys():
amt = int(input("Enter the bill amount: "))
self.cust_dict[custn].pay_bill(amt)
print("\n\n")
else:
print("Place an order first!\n\n")
def showDetails(self):
custn = int(input("Enter your customer number: "))
if custn in self.cust_dict.keys():
self.cust_dict[custn].show()
print("\n\n")
else:
print("Place an order first!\n\n")
#Main code
orest = Restaurant()
while True:
print("Welcome to our restaurant!")
print("1. See the menu.")
print("2. Place order.")
print("3. Pay the bill.")
print("4. Get customer details.")
print("5. Exit.")
choice = int(input("What would you like to do?\n"))
if choice == 1:
orest.showMenu()
elif choice == 2:
orest.placeOrder()
elif choice == 3:
orest.payBill()
elif choice == 4:
orest.showDetails()
elif choice == 5:
print("\n\nThanks for visiting!!\n\n")
break
else:
print("Invalid choice!!")
Interface vs Implementation of a class
The implementation of a class is the actual code of the class. This shows how an object does what it does.
The interface is the methods that a class provides. This shows what an object can do.
We created the Groot class, and at that time we were the creators of the class. We dealt with what the object of our class will be doing. Implementation.
Then, when we were making the Restaurant class we used the Groot class. We used the methods not caring what their code does. Interface.
The interface of a class is the documentation of all the methods and related parameters in the class. The implementation is the actual code of the class.
What you need to know depends on your role. Creator –> implementation and User –> interface.
Conclusion
This was a long post. We learned how to use the classes that we make.
For this, we made some changes in the code of our existing class.
Next, we used our class in a way it can manage multiple users.
We did this first using a list then, using a dictionary, and finally using a new class.
Hope you got clear about managing multiple instances.
You can consider reading this chapter of Irv Kalb’s book OOP Python. It would make your concepts even more clear.
Challenge π§ββοΈ
Your challenge is simple. You need to make the library class below just as we made the Groot class above. You can rename the class. You just need to make the final version of it. Which means a class to manage the library class.
Maybe it is not quite simple. But, it’s great to make this concept crystal clear in your head.
Ok so solve this challenge well and Iβll see you in the next post.
Till then take care and have a great timeπ. Bye-bye.π