Managing Multiple Instances (Objects): Python OOP

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!

Groot's gif

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.

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, and
  • show() – 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.πŸ‘‹

Leave a Reply