A complete walkthrough of polymorphism, method overriding, and super() — explained step by step through a real business profit tracker for Disco Hub and Shawarma Store in El Nido, Palawan. Every class, every method, every design decision explained.
Imagine you own two very different businesses in the bustling tourist town of El Nido, Palawan: a high‑energy disco hub and a cozy shawarma store. Both generate daily sales, incur daily expenses, and pay workers’ salaries. But each has unique costs — the disco pays a DJ fee, the shawarma shop loses money to ingredient waste. How can you model these businesses in Python without duplicating code and while keeping the ability to treat them uniformly?
The answer is polymorphism through inheritance and method overriding. The BusinessUnit base class captures everything common to both businesses: sales, expenses, salaries, and basic profit calculations. Then DiscoHub and ShawarmaStore inherit from it, overriding only the methods that need to account for their unique costs. Thanks to polymorphism, you can store both types in a single list and call the same methods (generate_report()) — Python automatically runs the correct version for each object.
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() to extend parent logic, to the importance of keeping method signatures identical for polymorphism.
super(), the DRY principle, designing extensible class hierarchies, and a clean command‑line report generator — all in one concise, real‑world program.
The program consists of one base class and two subclasses. The flow is simple: create instances, store them in a list, loop over the list, and call generate_report() on each. The magic of polymorphism ensures each object’s overridden methods are used automatically.
name, daily_sales, daily_expenses, workers_salary. Provides methods to calculate net expenses, gross profit, net profit, and a formatted report.dj_entertainment_fee. Overrides calculate_net_expenses() to include this fee using super().ingredient_waste. Overrides calculate_gross_profit() to subtract waste from the base gross profit.# This program uses only built-in Python features. # No import statements are needed.
Unlike many real‑world programs that rely on external modules, this example stays completely self‑contained. Everything you need — classes, methods, string formatting, and the if __name__ == "__main__" guard — is part of Python’s core. This keeps the focus on OOP concepts without distractions.
class BusinessUnit: def __init__(self, name: str, daily_sales: float, daily_expenses: float, workers_salary: float): self.name = name self.daily_sales = daily_sales self.daily_expenses = daily_expenses self.workers_salary = workers_salary def calculate_net_expenses(self) -> float: return self.daily_expenses + self.workers_salary def calculate_gross_profit(self) -> float: return self.daily_sales - self.daily_expenses def calculate_net_profit(self) -> float: return self.daily_sales - self.calculate_net_expenses() def generate_report(self): print(f"\n{'='*60}") print(f"EL NIDO BUSINESS TRACKER - {self.name}") print(f"{'='*60}") print(f"Daily Sales : PHP {self.daily_sales:,.2f}") print(f"Daily Expenses : PHP {self.daily_expenses:,.2f}") print(f"Workers' Salary : PHP {self.workers_salary:,.2f}") print(f"Net Expenses : PHP {self.calculate_net_expenses():,.2f}") print(f"Gross Profit : PHP {self.calculate_gross_profit():,.2f}") print(f"Net Profit : PHP {self.calculate_net_profit():,.2f}") print(f"{'='*60}\n")
The BusinessUnit class is the foundation. It defines four attributes that every business shares and four methods that calculate various profit metrics. The type hints (name: str, etc.) serve as documentation; Python does not enforce them at runtime. The generate_report() method prints a well‑formatted summary, using f‑strings with the :,.2f format specifier to display numbers with commas and two decimal places — essential for readability when dealing with Philippine Peso amounts.
Notice that calculate_net_profit() calls self.calculate_net_expenses() rather than repeating the formula. This is a deliberate design: it ensures that any subclass that overrides calculate_net_expenses() will automatically affect the net profit calculation. This is the key to polymorphism — the base method uses a hook that child classes can customise.
class DiscoHub(BusinessUnit): def __init__(self, name: str, daily_sales: float, daily_expenses: float, workers_salary: float, dj_entertainment_fee: float): super().__init__(name, daily_sales, daily_expenses, workers_salary) self.dj_entertainment_fee = dj_entertainment_fee def calculate_net_expenses(self) -> float: base_net = super().calculate_net_expenses() return base_net + self.dj_entertainment_fee
A disco hub has an extra cost that a generic business does not: the DJ entertainment fee. The constructor takes this additional parameter, then calls the parent constructor using super().__init__(...) to initialise the common attributes. This avoids duplicating the four lines of attribute assignment from BusinessUnit.
The overridden calculate_net_expenses() first retrieves the base net expenses by calling super().calculate_net_expenses() — which returns daily_expenses + workers_salary — and then adds the DJ fee. This is the canonical way to extend a parent method: reuse the parent logic and then augment it. The method signature (no parameters, returns float) is identical to the parent’s, satisfying the Liskov substitution principle.
class ShawarmaStore(BusinessUnit): def __init__(self, name: str, daily_sales: float, daily_expenses: float, workers_salary: float, ingredient_waste: float): super().__init__(name, daily_sales, daily_expenses, workers_salary) self.ingredient_waste = ingredient_waste def calculate_gross_profit(self) -> float: return self.daily_sales - self.daily_expenses - self.ingredient_waste
The shawarma store introduces a different kind of unique cost: ingredient waste. Unlike the disco’s fee, waste directly reduces the gross profit rather than increasing expenses. Therefore, the class overrides calculate_gross_profit() instead of calculate_net_expenses(). It does not call super() because it completely replaces the parent’s logic — the parent formula (daily_sales - daily_expenses) is no longer sufficient.
Notice that the method signature remains the same: no parameters, returns a float. This consistency is vital for polymorphism. Also, the overridden version still uses the parent’s attributes (daily_sales, daily_expenses) because they are inherited. The new attribute ingredient_waste is used only here.
if __name__ == "__main__": print("🚀 El Nido Business Tracker - Disco Hub & Shawarma Store") print("Demonstrating Polymorphism in Python\n") disco = DiscoHub( name="Vibe Palace Disco Hub", daily_sales=85000, daily_expenses=32000, workers_salary=15000, dj_entertainment_fee=8000 ) shawarma = ShawarmaStore( name="El Nido Shawarma Hub", daily_sales=42000, daily_expenses=18000, workers_salary=9000, ingredient_waste=2500 ) businesses = [disco, shawarma] for business in businesses: business.generate_report()
This is where everything comes together. Two objects of different types are created and placed in a single list. The loop iterates over the list and calls generate_report() on each. Thanks to polymorphism, Python looks up the method at runtime and executes the version belonging to the object’s actual class.
For the disco object, generate_report() (inherited from BusinessUnit) calls calculate_net_expenses(), which is the overridden version in DiscoHub that includes the DJ fee. For the shawarma object, calculate_gross_profit() called from within generate_report() uses the overridden version that subtracts ingredient waste. The same report method produces correct, business‑specific results without any if statements or type checks.
Polymorphism (from Greek “many forms”) in Python means that the same method call can trigger different implementations depending on the object’s type. It is one of the four fundamental pillars of OOP (along with encapsulation, inheritance, and abstraction). In this program, polymorphism is achieved through inheritance and method overriding.
When you write business.generate_report(), Python:
business (e.g., DiscoHub).generate_report in that class. Not found.BusinessUnit). Found — so it calls BusinessUnit.generate_report(business).self.calculate_net_expenses() is called, Python again looks at the actual class of self (which is still DiscoHub) and finds the overridden version. That version runs.This dynamic dispatch happens at runtime, not compile time. It makes your code flexible: you can add new business types later, and the existing loop will handle them automatically as long as they follow the same interface (i.e., they have a generate_report method).
DiscoHub and ShawarmaStore can be used anywhere a BusinessUnit is expected — they don’t add new required methods and they don’t change the meaning of existing methods in a way that would surprise the caller.Overriding a method means providing a new implementation in a child class that replaces the parent’s version. The child can either completely replace the parent logic (like ShawarmaStore.calculate_gross_profit) or extend it (like DiscoHub.calculate_net_expenses).
super()super() gives you access to the parent class’s methods. Use it when you want to reuse the parent’s logic as part of your overridden method. In DiscoHub, calling super().calculate_net_expenses() avoids recalculating the base net expenses. This keeps your code DRY (Don’t Repeat Yourself) and ensures that if the base formula ever changes (e.g., adding a new type of expense), the child automatically inherits the update.
In contrast, ShawarmaStore does not call super().calculate_gross_profit() because the parent’s formula is not part of the correct result — waste is subtracted, not added. Complete replacement is the right choice here.
super() in __init__?If you omit super().__init__() in DiscoHub.__init__, the attributes name, daily_sales, etc., would never be set on the object. Accessing them later would raise an AttributeError. Always call the parent constructor when you override __init__, unless you have a very specific reason not to.
Always call super().__init__(...) in the child’s __init__ to initialise inherited attributes.
The overridden method must accept the same parameters (and return a compatible type) as the parent. Otherwise, code that expects the parent’s signature will break.
BusinessUnit contains only what is common to all businesses. Unique costs are pushed down to subclasses. This is the Single Responsibility Principle.
super() to extend, not replaceWhen your override needs the parent’s result, call super().method() and then augment it. This avoids duplication and centralises logic.
Solidify your understanding by extending the program:
DiveShop class that inherits from BusinessUnit and adds an equipment_rental_cost. Override calculate_net_expenses to include this cost. Instantiate it and add to the list.Discountable mixin with a discount_percent attribute and a apply_discount(amount) method. Modify ShawarmaStore to also inherit from this mixin and apply a 10% discount to gross profit for loyalty program.businesses.json file and dynamically creates the appropriate objects (using a factory pattern). Save reports to separate text files.class BusinessUnit: def __init__(self, name: str, daily_sales: float, daily_expenses: float, workers_salary: float): self.name = name self.daily_sales = daily_sales self.daily_expenses = daily_expenses self.workers_salary = workers_salary def calculate_net_expenses(self) -> float: return self.daily_expenses + self.workers_salary def calculate_gross_profit(self) -> float: return self.daily_sales - self.daily_expenses def calculate_net_profit(self) -> float: return self.daily_sales - self.calculate_net_expenses() def generate_report(self): print(f"\n{'='*60}") print(f"EL NIDO BUSINESS TRACKER - {self.name}") print(f"{'='*60}") print(f"Daily Sales : PHP {self.daily_sales:,.2f}") print(f"Daily Expenses : PHP {self.daily_expenses:,.2f}") print(f"Workers' Salary : PHP {self.workers_salary:,.2f}") print(f"Net Expenses : PHP {self.calculate_net_expenses():,.2f}") print(f"Gross Profit : PHP {self.calculate_gross_profit():,.2f}") print(f"Net Profit : PHP {self.calculate_net_profit():,.2f}") print(f"{'='*60}\n") class DiscoHub(BusinessUnit): def __init__(self, name: str, daily_sales: float, daily_expenses: float, workers_salary: float, dj_entertainment_fee: float): super().__init__(name, daily_sales, daily_expenses, workers_salary) self.dj_entertainment_fee = dj_entertainment_fee def calculate_net_expenses(self) -> float: base_net = super().calculate_net_expenses() return base_net + self.dj_entertainment_fee class ShawarmaStore(BusinessUnit): def __init__(self, name: str, daily_sales: float, daily_expenses: float, workers_salary: float, ingredient_waste: float): super().__init__(name, daily_sales, daily_expenses, workers_salary) self.ingredient_waste = ingredient_waste def calculate_gross_profit(self) -> float: return self.daily_sales - self.daily_expenses - self.ingredient_waste if __name__ == "__main__": print("🚀 El Nido Business Tracker - Disco Hub & Shawarma Store") print("Demonstrating Polymorphism in Python\n") disco = DiscoHub( name="Vibe Palace Disco Hub", daily_sales=85000, daily_expenses=32000, workers_salary=15000, dj_entertainment_fee=8000 ) shawarma = ShawarmaStore( name="El Nido Shawarma Hub", daily_sales=42000, daily_expenses=18000, workers_salary=9000, ingredient_waste=2500 ) businesses = [disco, shawarma] for business in businesses: business.generate_report()
BusinessUnit and two subclasses that override specific methods to account for unique costs (DJ fee, ingredient waste). A single loop calls the same methods on both objects, and the correct versions run automatically.calculate_net_expenses. Ingredient waste directly reduces the revenue after expenses, so it fits better in calculate_gross_profit. Overriding the appropriate method keeps the class design intuitive and follows the principle of least surprise.if statements. Polymorphism allows you to add new business types without modifying existing code—just create a new subclass and it works. Flags would require updating every conditional.disco.py and run python disco.py from your terminal. You will see the formatted reports for both businesses printed to the console.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 El Nido Business Tracker, from the base class to the polymorphic loop. You now understand how method overriding, super(), and runtime dispatch work together to create flexible, maintainable code.
Polymorphism is everywhere in Python — from built‑in functions like len() to frameworks like Django. The same principles you learned here will help you design systems that are easy to extend and debug.
Your next step? Take the extension challenges seriously. Add a DiveShop, implement a discount mixin, or load data from JSON. Each challenge will deepen your understanding.
💬 Leave a Comment
💡 Comments are stored locally in this session. Be respectful and constructive.