A complete walkthrough of inheritance, polymorphism, method overriding, and super() — explained step by step through a real Italian restaurant ordering system in El Nido, Palawan. Every class, every method, every design decision explained.
Welcome to Pasta & Pizza Hub – a Python command-line program that simulates a bustling Italian restaurant in the heart of El Nido, Palawan. Customers can choose from 20 different pizzas and 20 pastas, place an order, see a detailed summary, and watch as the kitchen prepares each item with a unique message. Behind this simple user experience lies a powerful demonstration of object-oriented programming: inheritance and polymorphism.
The program defines a base class Product that holds a name and price, and provides generic methods get_info() and process_order(). Two subclasses, Pizza and Pasta, inherit from Product. Each adds its own attribute (crust_type for pizza, sauce_type for pasta) and overrides the two methods to give them a unique flavour. When the final order is processed, a simple loop calls process_order() on every item – and Python automatically runs the correct version (baking message 🍕 for pizzas, boiling message 🍝 for pastas) thanks to polymorphism.
This guide walks through every line of the program, explaining not just what the code does, but why each design choice was made – from using super() in constructors, to storing products in a dictionary with clever codes, to validating user input.
super(), the DRY principle, dictionary‑based product lookup, input validation with try/except, and a realistic order flow – all in one concise, real‑world program.
The program consists of one base class (Product) and two subclasses (Pizza and Pasta). It creates 20 pizzas and 20 pastas with realistic names and prices, stores them in a dictionary using codes PIZ01 to PIZ20 and PAS01 to PAS20, displays the full menu, lets the user add items to a cart, calculates totals, collects payment details, and finally shows polymorphic preparation messages.
name, price, calculate_total(), get_info(), and process_order(). Serves as the blueprint for any sellable item.crust_type (default "Thin"). Overrides get_info() to include crust info and process_order() to print a baking message.sauce_type (default "Tomato"). Overrides get_info() to include sauce info and process_order() to print a boiling message.import randomThe only module imported is random, which is part of Python’s standard library. It is used at the very end to generate a random 6‑digit order confirmation code (random.randint(100000, 999999)). This adds a realistic touch – after payment, the customer receives a unique code to show at pickup. Everything else (classes, input handling, loops) uses pure Python.
class Product: def __init__(self, name: str, price: float): self.name = name self.price = price def calculate_total(self, quantity: int) -> float: return self.price * quantity def get_info(self) -> str: return f"{self.name} - PHP {self.price:.2f}" def process_order(self, quantity: int): print(f"Preparing {quantity} x {self.name}...")
Product is the foundation. It defines two attributes every product must have: a name and a price. The method calculate_total() returns the price multiplied by quantity – a simple but essential formula. get_info() returns a formatted string for display, and process_order() prints a generic preparation message. This class is intentionally kept minimal; all specialised behaviour will be added by subclasses.
Type hints (name: str, price: float) are optional but serve as built‑in documentation. The f"{self.price:.2f}" format ensures prices always show two decimal places.
class Pizza(Product): def __init__(self, name: str, price: float, crust_type: str = "Thin"): super().__init__(name, price) self.crust_type = crust_type def get_info(self) -> str: return f"Pizza: {self.name} ({self.crust_type} Crust) - PHP {self.price:.2f}" def process_order(self, quantity: int): print(f"Baking {quantity} x {self.name} pizza... 🍕")
The Pizza class introduces a new attribute, crust_type, with a default value of "Thin". The constructor uses super().__init__(name, price) to delegate the initialisation of the inherited attributes to the parent class. This is a clean application of the DRY principle – we don’t repeat the assignment of self.name and self.price.
Both get_info() and process_order() are overridden. get_info() now includes the crust type in the description, making the menu more informative. process_order() prints a playful baking message with a pizza emoji. The method signatures (parameters and return types) remain identical to the parent’s – this is crucial for polymorphism.
class Pasta(Product): def __init__(self, name: str, price: float, sauce_type: str = "Tomato"): super().__init__(name, price) self.sauce_type = sauce_type def get_info(self) -> str: return f"Pasta: {self.name} ({self.sauce_type} Sauce) - PHP {self.price:.2f}" def process_order(self, quantity: int): print(f"Boiling {quantity} x {self.name} pasta... 🍝")
The Pasta class mirrors Pizza but with a different unique attribute: sauce_type. Again, the parent constructor is called via super(). The overridden get_info() mentions the sauce, and process_order() prints a boiling message with a pasta emoji.
This symmetry is intentional – it shows how two different subclasses can each extend the base class in their own way while still conforming to the same interface. This is the essence of polymorphism.
pizza_names = ["Margherita", "Pepperoni", ...] # 20 names pizza_prices = [220, 280, ...] # 20 prices pasta_names = ["Spaghetti Bolognese", ...] # 20 names pasta_prices = [180, 210, ...] # 20 prices products = {} for i in range(20): code = f"PIZ{i+1:02d}" products[code] = Pizza(pizza_names[i], pizza_prices[i]) for i in range(20): code = f"PAS{i+1:02d}" products[code] = Pasta(pasta_names[i], pasta_prices[i])
Two lists hold the names and prices of 20 pizzas and 20 pastas. A dictionary named products maps human‑readable codes (like PIZ05) to the actual product objects. Using a dictionary gives O(1) lookup – when the user enters a code, we can instantly retrieve the corresponding object.
The f"PIZ{i+1:02d}" format creates codes with leading zeros, e.g., PIZ01, PIZ02, …, PIZ20. This ensures consistent code length and makes the menu look professional. The same logic is applied to pastas with the prefix PAS.
if choice in products.The main program displays the menu (by iterating over the dictionary keys in order), then enters a loop where the user can type product codes. The loop continues until the user types done.
cart = [] while True: choice = input("\nProduct code: ").strip().upper() if choice == "DONE": break if choice in products: try: qty = int(input(f"Quantity for {choice}: ")) if qty > 0: cart.append((products[choice], qty)) print(f"✅ Added {qty} × {products[choice].name}") else: print("Quantity must be positive.") except ValueError: print("Invalid quantity.") else: print("❌ Invalid code. Please check the menu.")
The input is stripped and converted to uppercase so that piz05 works just like PIZ05. The code is checked against the dictionary keys. If valid, the program asks for a quantity, validates it with a try/except to catch non‑numeric input, and ensures the quantity is positive. The product object and quantity are stored as a tuple in the cart list.
After ordering, the cart is displayed with itemised totals, the grand total is calculated, and the user enters their name, phone, and payment method. A simple confirmation step follows; if confirmed, a random order code is generated and the polymorphic order processing begins.
print("\n🔧 Preparing your order now:") for item, qty in cart: item.process_order(qty)
This loop is the heart of polymorphism. Even though cart contains both Pizza and Pasta objects, calling process_order() on each triggers the correct overridden method – baking 🍕 for pizzas, boiling 🍝 for pastas. No if statements, no type checking – just clean, polymorphic dispatch.
Polymorphism (from Greek “many forms”) means that the same method call can behave differently depending on the object’s actual class. In Python, this is achieved through inheritance and method overriding.
process_order to runWhen the loop calls item.process_order(qty), Python:
item (e.g., Pizza).process_order in that class. Found – so it calls Pizza.process_order(item, qty).Pizza, Python would look in the parent class Product.This dynamic dispatch happens at runtime, giving you tremendous flexibility. You can add a new subclass (e.g., Salad) with its own process_order, and the loop will handle it without any modifications – that’s the open/closed principle in action.
Pizza and Pasta can be placed in the cart and passed to any function that expects a Product – they don’t break the program.Overriding a method means providing a new implementation in a child class that replaces the parent’s version. In this program, both get_info() and process_order() are overridden.
super()super() is used in the constructor to call the parent’s __init__ and initialise inherited attributes. This avoids repeating the assignment of self.name and self.price. If you forget super().__init__, those attributes will never be set, leading to an AttributeError later.
In the overridden methods themselves, we don’t call super() because we are completely replacing the parent’s behaviour. If we wanted to extend the parent method (e.g., print the generic message and an emoji), we could call super().process_order(quantity) first.
super().__init__If you omit the parent constructor call, the base attributes (name, price) are never set. Always call super().__init__(...) when you override the constructor.
process_order in Pizza must accept the same parameters (self, quantity) as in Product. If you add an extra parameter, code that calls the method polymorphically will break.
Mapping codes to objects with a dictionary is efficient and clean. It also allows simple membership testing with if code in products.
try/exceptWrapping int(input()) in a try block catches non‑numeric entries gracefully. Always provide a helpful error message and continue the loop.
.strip() and .upper() on inputThis makes the program more forgiving: " piz05 " becomes "PIZ05" and matches the dictionary key.
Solidify your understanding by extending the program:
Salad class that inherits from Product and adds a dressing attribute. Override get_info() and process_order() (e.g., "Tossing salad... 🥗"). Add 10 salads to the menu with codes SAL01 to SAL10.order_.json . Include customer details, items with quantities, and total. Use the json module with ensure_ascii=False to preserve emojis.import random class Product: def __init__(self, name: str, price: float): self.name = name self.price = price def calculate_total(self, quantity: int) -> float: return self.price * quantity def get_info(self) -> str: return f"{self.name} - PHP {self.price:.2f}" def process_order(self, quantity: int): print(f"Preparing {quantity} x {self.name}...") class Pizza(Product): def __init__(self, name: str, price: float, crust_type: str = "Thin"): super().__init__(name, price) self.crust_type = crust_type def get_info(self) -> str: return f"Pizza: {self.name} ({self.crust_type} Crust) - PHP {self.price:.2f}" def process_order(self, quantity: int): print(f"Baking {quantity} x {self.name} pizza... 🍕") class Pasta(Product): def __init__(self, name: str, price: float, sauce_type: str = "Tomato"): super().__init__(name, price) self.sauce_type = sauce_type def get_info(self) -> str: return f"Pasta: {self.name} ({self.sauce_type} Sauce) - PHP {self.price:.2f}" def process_order(self, quantity: int): print(f"Boiling {quantity} x {self.name} pasta... 🍝") if __name__ == "__main__": print("🌟 Welcome to Pasta & Pizza Hub - El Nido's Finest Italian Spot 🌟\n") pizza_names = [ "Margherita", "Pepperoni", "Hawaiian", "Meat Lovers", "Veggie Supreme", "Four Cheese", "BBQ Chicken", "Seafood Delight", "Supreme", "Truffle Mushroom", "Spicy Italian", "Chicken Tikka", "Pesto Basil", "Tuna Mayo", "Bacon Ranch", "Mushroom Delight", "Olive and Feta", "Pineapple Special", "Classic Italian", "Garlic Shrimp" ] pizza_prices = [220, 280, 250, 320, 260, 290, 310, 340, 300, 350, 270, 295, 265, 240, 305, 255, 275, 285, 230, 315] pasta_names = [ "Spaghetti Bolognese", "Fettuccine Alfredo", "Penne Arrabbiata", "Classic Lasagna", "Creamy Carbonara", "Pesto Genovese", "Shrimp Scampi", "Beef Stroganoff", "Cheese Ravioli", "Spinach Tortellini", "Potato Gnocchi", "Linguine Vongole", "Farfalle Primavera", "Rigatoni Amatriciana", "Fusilli Puttanesca", "Cannelloni Ricotta", "Lemon Orzo", "Tagliatelle Bolognese", "Bucatini Cacio e Pepe", "Macaroni Cheese" ] pasta_prices = [180, 210, 195, 250, 220, 200, 230, 240, 215, 205, 190, 225, 185, 235, 245, 260, 170, 255, 265, 175] products = {} for i in range(20): code = f"PIZ{i+1:02d}" products[code] = Pizza(pizza_names[i], pizza_prices[i]) for i in range(20): code = f"PAS{i+1:02d}" products[code] = Pasta(pasta_names[i], pasta_prices[i]) print("=== FULL MENU (20 Pizzas + 20 Pastas) ===") print("\nPIZZAS:") for code in [f"PIZ{i:02d}" for i in range(1, 21)]: print(f" {code}: {products[code].get_info()}") print("\nPASTAS:") for code in [f"PAS{i:02d}" for i in range(1, 21)]: print(f" {code}: {products[code].get_info()}") cart = [] print("\n=== PLACE YOUR ORDER ===") print("Enter product code (e.g. PIZ05 or PAS12). Type 'done' to finish.") while True: choice = input("\nProduct code: ").strip().upper() if choice == "DONE": break if choice in products: try: qty = int(input(f"Quantity for {choice}: ")) if qty > 0: cart.append((products[choice], qty)) print(f"✅ Added {qty} × {products[choice].name}") else: print("Quantity must be positive.") except ValueError: print("Invalid quantity.") else: print("❌ Invalid code. Please check the menu.") if not cart: print("\nNo items ordered. Thank you for visiting!") else: total = 0.0 print("\n=== ORDER SUMMARY ===") for item, qty in cart: item_total = item.calculate_total(qty) total += item_total print(f"{qty} × {item.name:25} = PHP {item_total:,.2f}") print(f"{'-'*50}") print(f"GRAND TOTAL: PHP {total:,.2f}") name = input("\nEnter your full name: ").strip() phone = input("Enter your phone number: ").strip() while True: payment_method = input("Payment method (cash or credit): ").strip().lower() if payment_method in ["cash", "credit"]: break print("Please type 'cash' or 'credit'.") if payment_method == "credit": input("Enter card number (demo): ") print("✅ Card validated successfully.") confirm = input(f"\nConfirm payment of PHP {total:,.2f}? (y/n): ").strip().lower() if confirm == "y": order_code = random.randint(100000, 999999) print("\n🎉 PAYMENT CONFIRMED! Thank you, " + name + "!") print(f"📱 Your order code {order_code} has been sent via SMS to {phone}") print("\n🔧 Preparing your order now:") for item, qty in cart: item.process_order(qty) print(f"\n✅ Your order is being prepared. Please show code " + str(order_code) + " at the pickup counter.") print("We hope to see you again at Pasta & Pizza Hub!") else: print("\nOrder cancelled. Thank you for visiting Pasta & Pizza Hub!")
Stay connected for more Python guides, OOP tutorials, and tech content from the Philippines. Follow on your preferred platform:
Get new Python tutorials, OOP guides, and tech articles from Valleys & Bytes delivered to your inbox — no spam, ever.
🔒 No spam. Unsubscribe anytime. Your email is never shared.
You have walked through every line of Pasta & Pizza Hub, from the base class to the polymorphic preparation loop. You now understand how method overriding, super(), and dictionary‑based product lookup work together to create a flexible, realistic ordering system.
Inheritance and polymorphism are fundamental tools in any Python developer's toolbox. They allow you to write code that is both reusable and easy to extend. The same principles you learned here appear in web frameworks, game engines, and data science libraries.
Your next step? Take the extension challenges seriously. Add a Salad class, implement discounts, or save orders to JSON. Each challenge will deepen your understanding and give you code you can proudly show off.
💬 Leave a Comment
💡 Comments are stored locally in this session. Be respectful and constructive.