ADVERTISEMENT
Python · Inheritance · OOP · Philippines

CornRice.py

Python OOP · Inheritance & Polymorphism Guide

A complete walkthrough of single inheritance, method overriding, and runtime polymorphism — explained step by step through a real Filipino corn and rice agri-market application. Every class, every method call, every design decision explained.

🌽 Corn & Rice Market 🧱 1 Base + 2 Child Classes 🔁 Method Overriding 🦆 Runtime Polymorphism 🛡️ Error Handling 🐍 Python 3
SCROLL TO EXPLORE
INTRODUCTION

What Is CornRice.py and Why Does It Matter?

Walk into any palengke in the Philippines and you will find stalls selling both mais (corn) and bigas (rice) side by side. Both are grains, both are priced per kilogram, and both can be purchased in varying quantities — yet corn is corn and rice is rice. They share a common nature but differ in how they present themselves. This is exactly the intuition behind CornRice.py, and it is also the intuition behind one of the most fundamental concepts in object-oriented programming: inheritance with method overriding.

In this program, a single Product base class captures everything that is common to all items for sale — a name, a price, the ability to display itself, and the ability to calculate a total for a given quantity. Two child classes, Corn and Rice, inherit all of this but override just one method — display() — to prepend their own category label. That one override is enough to enable runtime polymorphism: a loop can call product.display() on every item in a mixed list and each object produces the right output for its own type, without a single if or isinstance check in sight.

This guide walks through every line in a logical, progressive order. It explains not just what the code does but why every decision was made — from why Corn does not need its own __init__, to how calculate_total() is inherited without repetition, to exactly what happens inside the try/except block and why two exception types are caught separately.

🐍
What you will learn from this guide: Defining a base class with shared state and behaviour, single inheritance with class Child(Parent) syntax, inheriting a constructor without repeating it, overriding a specific method in a child class, runtime polymorphism through a polymorphic list, enumerate() for numbered display, try/except ValueError with a manual raise, and the if __name__ == "__main__" entry guard — all in one cohesive, runnable Filipino agri-market program.
💡
Who this guide is for: Python beginners who understand variables and functions and are ready to learn their first object-oriented program. No prior knowledge of classes is assumed — every concept is explained from the ground up using the context of a real Filipino agri-market.
ADVERTISEMENT
SECTION 01

Program Overview

CornRice.py is built around three classes and one main() function. The class hierarchy is intentionally simple: one parent and two children. Before diving into the individual sections, it helps to see the full picture of how these classes relate and how data flows from program startup to a printed receipt.

Class 1 — Product
The base class for every item in the shop. Holds name and price. Provides display() for a plain formatted string and calculate_total(quantity) for the total cost. All products inherit from it.
Class 2 — Corn
Inherits from Product. Does not define __init__ — uses Product's constructor directly. Overrides only display() to prepend the [CORN VARIETY] category label.
Class 3 — Rice
Mirrors Corn exactly in structure. Overrides display() to prepend [RICE VARIETY]. Together with Corn, it demonstrates polymorphism — same method call, different label.
main() — Entry Point
Creates a mixed list of Corn and Rice objects, displays them with enumerate(), takes user input, validates it, calculates the total, and prints a formatted receipt — all within a try/except block.
🚀Start
main() → 8 products created in a mixed list
📋Display
Polymorphic loop prints numbered catalog
🔢Select
User picks product number and quantity
Validate
try/except catches invalid or out-of-range input
🧾Receipt
calculate_total() → formatted receipt printed
SECTION 02

The Product Base Class

class Product
class Product:
    def __init__(self, name: str, price: float):
        self.name = name
        self.price = price

    def display(self) -> str:
        return f"{self.name} - ₱{self.price:.2f} per kilogram"

    def calculate_total(self, quantity: int) -> float:
        return self.price * quantity

The Product class is the root of the entire hierarchy. It answers one fundamental question: what must every item for sale have in common? The answer is three things — a display name (name), a price in Philippine Pesos (price), the ability to format itself as a readable string (display()), and the ability to calculate a purchase total for any quantity (calculate_total()). These are defined exactly once here and inherited automatically by every child class.

The type annotations (name: str, price: float) are informational — Python does not enforce them at runtime — but they serve as built-in documentation that tells any reader exactly what types are expected. The format specifier :.2f in display() ensures the price always appears with exactly two decimal places regardless of the actual float value, producing consistent output like ₱35.00 rather than ₱35.0.

MemberTypePurpose
namestrHuman-readable product name used in display and receipt output
pricefloatUnit price in Philippine Pesos per kilogram, used in total calculation
display()→ strReturns a formatted catalog string; overridden by child classes
calculate_total(quantity)→ floatReturns price × quantity; inherited unchanged by all children
🏗️
Why not put category labels in Product.display()? Because Product does not know what category it is — that knowledge belongs to the child classes. Putting category logic in the base class would mean every future product type must modify the base class, or that Product would need to hold a category attribute. Letting each child override display() keeps the base class clean and each child class self-contained.
SECTION 03

The Corn Class — Inheriting and Overriding

class Corn(Product)
class Corn(Product):
    def display(self) -> str:
        return f"[CORN VARIETY] {self.name} - ₱{self.price:.2f} per kilogram"

The class declaration Corn(Product) is the syntax for single inheritance. The parentheses hold the parent class. From this one line, Corn automatically inherits __init__, the name and price attributes, the inherited display() from Product, and calculate_total() — everything Product has. The only thing Corn does is replace display() with its own version.

Crucially, Corn does not define __init__. This is intentional and correct. When Python looks for __init__ on a Corn instance and does not find it defined in Corn, it walks up the inheritance chain and finds Product.__init__. That method runs exactly as written in Product, setting self.name and self.price on the Corn instance. This means Corn("Yellow Corn (Mais Dilaw)", 35.00) works perfectly — no code duplication required.

♻️
DRY Principle — Don't Repeat Yourself: Without inheritance, you would need to copy the __init__ body and calculate_total() into both Corn and Rice. If the pricing formula ever changed — say, adding a seasonal surcharge — you would have to update it in three places and risk getting them out of sync. With inheritance, there is one single source of truth in Product that both children share automatically.
What exactly does Corn override vs. inherit?
MemberSourceBehaviour
__init__Inherited from ProductSets self.name and self.price — no change needed
display()Overridden in CornPrepends [CORN VARIETY] to the output string
calculate_total()Inherited from ProductReturns self.price × quantity — no change needed
SECTION 04

The Rice Class — Parallel Structure

class Rice(Product)
class Rice(Product):
    def display(self) -> str:
        return f"[RICE VARIETY] {self.name} - ₱{self.price:.2f} per kilogram"

Rice is structurally identical to Corn. It inherits from Product, does not define __init__, and overrides only display() — this time prepending [RICE VARIETY]. This parallel structure is deliberate. When two classes differ in only one behaviour, they should be identical in structure everywhere else. Any future programmer reading the code immediately understands the pattern: the category label is the only thing that distinguishes Corn from Rice, and both live entirely in their respective display() overrides.

⚖️
Corn vs. Rice — the only difference: Corn.display() returns a string starting with [CORN VARIETY]. Rice.display() returns a string starting with [RICE VARIETY]. That is the entire difference between these two classes. Everything else — constructor, attributes, calculate_total() — is identical because both inherit it unchanged from Product. This is inheritance working exactly as intended.
Corn Products (3)
Yellow Corn (₱35.00), White Corn (₱38.00), Sweet Corn (₱52.00). All tagged [CORN VARIETY] in the catalog display.
Rice Products (5)
Sinandomeng (₱45.00), Dinorado (₱48.00), Jasmine (₱55.00), Brown Rice (₱62.00), Red Rice (₱70.00). All tagged [RICE VARIETY].
SECTION 05

Polymorphism — One Loop, Many Forms

main() — Product list and polymorphic display loop
    products = [
        Corn("Yellow Corn (Mais Dilaw)", 35.00),
        Corn("White Corn (Mais Puti)", 38.00),
        Corn("Sweet Corn (Mais Matamis)", 52.00),
        Rice("Sinandomeng Rice", 45.00),
        Rice("Dinorado Rice", 48.00),
        Rice("Jasmine Rice (Aromatic)", 55.00),
        Rice("Brown Rice (Pinoy Brown)", 62.00),
        Rice("Red Rice (Red Malagkit)", 70.00),
    ]

    for i, product in enumerate(products, 1):
        print(f"{i:2d}. {product.display()}")

The products list holds a mix of Corn and Rice objects. Python's type system allows this because both are subclasses of Product — they share a common interface. The for loop calls product.display() on every item without ever checking what type it is. This is runtime polymorphism: Python dispatches to Corn.display() for the first three items and to Rice.display() for the last five, producing the correct prefixed output each time.

The enumerate(products, 1) call deserves attention. enumerate() wraps any iterable and yields (index, item) tuples. The second argument 1 sets the starting index, so the catalog is numbered 1 through 8 rather than 0 through 7 — exactly what a human-facing menu should show. The format specifier :2d right-aligns the number in a two-character field, so item 1 and item 10 align neatly in the terminal.

💡
Without polymorphism — what the loop would look like: If display() was not overridden, the loop would need to check the type of each item manually: if isinstance(product, Corn): print("[CORN VARIETY] ..."). Every new product category would require a new elif branch inside the loop. With polymorphism, the loop never needs to change — adding a new class with its own display() override automatically works.
SECTION 06

Input Validation — Guarding Against Bad Data

main() — Input, validation, and error handling
    try:
        choice = int(input("\n🔢 Enter the number of the product you want: ")) - 1

        if choice < 0 or choice >= len(products):
            raise ValueError("Invalid product number!")

        selected_product = products[choice]
        quantity = int(input(f"📦 Enter quantity for {selected_product.name} (in kg): "))

        if quantity <= 0:
            raise ValueError("Quantity must be greater than zero!")

        total_cost = selected_product.calculate_total(quantity)
        # ... receipt printing ...

    except ValueError as e:
        print(f"\n❌ Invalid input! {e}")
        print("Please run the program again and enter valid numbers.")
    except Exception:
        print("\n❌ Unexpected error. Please try again.")

The entire interactive section of the program is wrapped in a try/except block. This is the standard Python pattern for protecting user-facing input code. The block has three distinct layers of validation, each guarding a different category of invalid input.

1
int() raises ValueError on non-numeric text
When the user types "abc" or leaves the field blank, int(input(...)) raises ValueError automatically. The program catches it in the except ValueError clause, prints a friendly message, and stops — no crash, no traceback.
2
Explicit range check raises ValueError manually
Even if the user enters a valid integer, it might be out of range — for example, 0 or 99. The if choice < 0 or choice >= len(products) check catches this and manually raises ValueError("Invalid product number!"). Using raise ValueError deliberately slots this logic into the same exception handler, keeping all input-error messaging in one place.
3
Quantity validation raises ValueError manually
A quantity of 0 or a negative number would produce a meaningless or negative total cost. The if quantity <= 0 check catches this with another explicit raise ValueError("Quantity must be greater than zero!"), printing a specific message so the user knows exactly what went wrong.
4
Bare except catches anything else
The final except Exception clause is a safety net for truly unexpected errors — file system issues, memory problems, or anything else that is not a ValueError. It does not print the exception message (which could expose confusing technical details to users) but simply signals that something went wrong and asks the user to try again.
SECTION 07

The Receipt — Polymorphism in the Output

main() — Receipt printing
        print("\n" + "=" * 60)
        print("🧾 OFFICIAL RECEIPT - Pinoy AgriMart")
        print("=" * 60)
        print(f"Product     : {selected_product.display()}")
        print(f"Quantity    : {quantity} kg")
        print(f"Unit Price  : ₱{selected_product.price:.2f}")
        print(f"Total Cost  : ₱{total_cost:.2f}")
        print("=" * 60)
        print("Thank you for shopping with us! 🇵🇭 Mabuhay!")

The receipt calls selected_product.display() again — the same polymorphic method used in the catalog loop. This is significant: the receipt does not need to know whether the selected product is a Corn or Rice. If the user selected Sinandomeng Rice, the receipt line reads [RICE VARIETY] Sinandomeng Rice - ₱45.00 per kilogram. If they selected Sweet Corn, it reads [CORN VARIETY] Sweet Corn (Mais Matamis) - ₱52.00 per kilogram. The same one line of receipt code handles both cases through polymorphism.

The total cost is computed by selected_product.calculate_total(quantity) — the method defined once in Product and inherited by both Corn and Rice. Whether the selected item is corn or rice is completely irrelevant to the calculation. This is the DRY principle in practice: one method, zero duplication, correct results for every product type.

🧾
Sample receipt output — 3 kg of Jasmine Rice: The receipt would display [RICE VARIETY] Jasmine Rice (Aromatic) - ₱55.00 per kilogram as the product line, Quantity: 3 kg, Unit Price: ₱55.00, and Total Cost: ₱165.00. The [RICE VARIETY] prefix in the product line comes from Rice.display() — polymorphism at work inside the receipt itself.
SECTION 08

The __name__ Guard — Safe Entry Point

CornRice.py — Final Lines
if __name__ == "__main__":
    main()

The if __name__ == "__main__" guard is a Python idiom that prevents main() from running when the file is imported as a module into another script. When Python runs a file directly from the terminal, it sets the special variable __name__ to the string "__main__". When the same file is imported by another script (import corn_rice), Python sets __name__ to the module's filename instead, so the condition is False and main() never runs.

This matters for reusability. If you later wanted to write a unit test that imports the Product, Corn, and Rice classes to test them in isolation, the test would import this file. Without the guard, importing the file would immediately start the interactive shopping session — which is not what a test runner expects. The guard ensures the classes are importable without side effects, making the code modular and testable.

BEST PRACTICES

Design Principles in CornRice.py

DRY — Don't Repeat Yourself
__init__ and calculate_total() are written exactly once in Product and inherited by both children. There is no copy of these methods in Corn or Rice. If the formula changes, one edit fixes everything.
Single Responsibility
Each class has one job. Product defines what a product is. Corn describes how a corn product displays itself. Rice describes how a rice product displays itself. main() orchestrates the user experience. No class does more than its role requires.
Open/Closed
The catalog loop and receipt code are open for extension — adding a Vegetable(Product) class with its own display() override requires zero changes to main()'s loop or receipt section. Both are closed for modification.
Specific Error Messages
Each raise ValueError carries a specific message string: "Invalid product number!" and "Quantity must be greater than zero!". These messages surface via except ValueError as e, giving the user actionable feedback rather than a generic error.
Type Annotations
Every method parameter and return type is annotated: name: str, price: float, quantity: int, -> str, -> float. These serve as built-in documentation and enable static type checkers like mypy to catch type errors before the program runs.
Enumerate for Menus
Using enumerate(products, 1) instead of a manual counter variable is the idiomatic Python way to produce numbered lists. It produces cleaner code, avoids off-by-one errors, and automatically adapts if products are added or removed.
FULL SOURCE

Complete CornRice.py Source Code

The complete, runnable source code. Save it as corn_rice.py and run it with python corn_rice.py.

corn_rice.py — Full Source
class Product:
    def __init__(self, name: str, price: float):
        self.name = name
        self.price = price

    def display(self) -> str:
        return f"{self.name} - ₱{self.price:.2f} per kilogram"

    def calculate_total(self, quantity: int) -> float:
        return self.price * quantity


class Corn(Product):
    def display(self) -> str:
        return f"[CORN VARIETY] {self.name} - ₱{self.price:.2f} per kilogram"


class Rice(Product):
    def display(self) -> str:
        return f"[RICE VARIETY] {self.name} - ₱{self.price:.2f} per kilogram"


def main():
    print("=" * 60)
    print("🇵🇭 Welcome to Pinoy AgriMart - Fresh Corn & Rice Shop (Philippines)")
    print("We sell high-quality local corn and rice varieties!")
    print("All prices are in Philippine Pesos (₱) per kilogram.")
    print("=" * 60)
    print("\n📋 AVAILABLE PRODUCTS:\n")

    products = [
        Corn("Yellow Corn (Mais Dilaw)",    35.00),
        Corn("White Corn (Mais Puti)",      38.00),
        Corn("Sweet Corn (Mais Matamis)",   52.00),
        Rice("Sinandomeng Rice",            45.00),
        Rice("Dinorado Rice",               48.00),
        Rice("Jasmine Rice (Aromatic)",     55.00),
        Rice("Brown Rice (Pinoy Brown)",    62.00),
        Rice("Red Rice (Red Malagkit)",     70.00),
    ]

    for i, product in enumerate(products, 1):
        print(f"{i:2d}. {product.display()}")

    print("\n" + "=" * 60)

    try:
        choice = int(input("\n🔢 Enter the number of the product you want: ")) - 1

        if choice < 0 or choice >= len(products):
            raise ValueError("Invalid product number!")

        selected_product = products[choice]
        quantity = int(input(f"📦 Enter quantity for {selected_product.name} (in kg): "))

        if quantity <= 0:
            raise ValueError("Quantity must be greater than zero!")

        total_cost = selected_product.calculate_total(quantity)

        print("\n" + "=" * 60)
        print("🧾 OFFICIAL RECEIPT - Pinoy AgriMart")
        print("=" * 60)
        print(f"Product     : {selected_product.display()}")
        print(f"Quantity    : {quantity} kg")
        print(f"Unit Price  : ₱{selected_product.price:.2f}")
        print(f"Total Cost  : ₱{total_cost:.2f}")
        print("=" * 60)
        print("Thank you for shopping with us! 🇵🇭 Mabuhay!")

    except ValueError as e:
        print(f"\n❌ Invalid input! {e}")
        print("Please run the program again and enter valid numbers.")
    except Exception:
        print("\n❌ Unexpected error. Please try again.")


if __name__ == "__main__":
    main()
Was this helpful?
Share This Guide
✨ AI-Powered Share Caption

Generate a ready-to-post social media caption for this guide. Pick your platform and tone, then click Generate.

Generating caption…
ADVERTISEMENT
FAQ

Frequently Asked Questions

CornRice.py is a Python command-line agri-market application that simulates a Filipino corn and rice shop. It demonstrates single inheritance by having Corn and Rice inherit from a shared Product base class. Both child classes override display() to prepend a category label, while inheriting __init__ and calculate_total() unchanged. The program handles product selection, quantity input, receipt generation, and full error handling through a try/except block.
When a child class does not define its own __init__, Python automatically uses the parent's through the Method Resolution Order. Since Corn and Rice need exactly the same constructor parameters as Product — just a name and a price — there is no reason to rewrite that code. This is the DRY principle in action. Defining a duplicate __init__ in Corn would be noise that adds maintenance burden without adding any value.
Method overriding means a child class provides its own implementation of a method that already exists in a parent class. In CornRice.py, both Corn and Rice override display(). When Python calls display() on a Corn instance, it finds Corn.display() first in the Method Resolution Order and uses it. The Product.display() version is skipped entirely. This is what allows each class to produce its own category-labelled output from the same method call.
The products list holds a mix of Corn and Rice objects. The for loop calls product.display() on each item without checking what type it is. Python dispatches to Corn.display() for Corn objects and to Rice.display() for Rice objects automatically at runtime. This is polymorphism — the same method call produces different output depending on the object's actual type, with no isinstance checks or if/elif branches in the calling code.
calculate_total() is identical for every product — it always multiplies self.price by the given quantity. There is no meaningful difference between how corn and rice calculate their totals. Defining it once in Product and letting both child classes inherit it is the DRY principle applied to methods. If the pricing formula ever changes — for example, to add a bulk discount — one edit in Product applies automatically to all product types.
Yes — and no changes to the loop or receipt code are needed. Simply define a new class that inherits from Product, such as class Vegetable(Product), and override display() to return a string starting with [VEGETABLE]. Add instances to the products list and the catalog loop, product selection, and receipt generation all work immediately with the new type. This is the open/closed principle — the program is open for extension and closed for modification.
🌽 Ready to Extend CornRice.py?

Now that you understand single inheritance and method overriding in CornRice.py, try adding a Vegetable(Product) class with a [VEGETABLE] prefix, a Legume class for beans and peanuts, or a shopping cart that lets the user buy multiple products in one session. Each extension requires zero changes to the existing catalog loop or receipt code — that is the power of inheritance and polymorphism.

COMMUNITY

Comments & Discussion

💬 Leave a Comment

💡 Comments are stored locally in this session. Be respectful and constructive.

M
Maria Santos
March 2026
The explanation of why Corn does not need its own __init__ was exactly what I was looking for. I always copied the constructor into every child class out of habit. Understanding that Python walks up the MRO to find it automatically was a lightbulb moment for me.
B
Ben Villanueva
March 2026
Using a Filipino palengke as the context for inheritance made it so much more intuitive than the usual Animal/Dog examples. I immediately understood why Corn and Rice share a base class but each need their own display label. Sobrang ganda ng tutorial na ito!
C
Carina Dela Cruz
March 2026
The section on why calculate_total() lives only in Product really drove the DRY principle home for me. I already knew not to repeat yourself, but this example showed me exactly which methods should be in the base class versus the children. Great practical guide!
CONNECT

Follow Glenn Junsay Pansensoy

Stay connected for more Python guides, OOP tutorials, and tech content from the Philippines. Follow on your preferred platform:

📘 🐦 💼