Python OOP Β· Philippine Rice Retail

Learning Python Through
Rice Retail β€”
A Complete Walkthrough

A step-by-step journey through Python OOP grounded in a real Philippine rice shop. Learn abstraction, classes, dictionaries, loops, f-strings, and error handling all in one cohesive program.

πŸ“… July 18, 2026 ⏱ 20 min read πŸŽ“ Intermediate 🐍 Python 3.11+ πŸ‡΅πŸ‡­ Philippines
Back to Abstraction Hub

Why Rice, Why Python?

🌾 Building a Philippine Rice Retail System in Python

Rice is not just food in the Philippines β€” it is identity, culture, and daily life. Sold in every barangay sari-sari store, every wet market palengke, and every SM Hypermarket aisle, rice is the single most purchased commodity in Filipino households.

This tutorial is built around a single, complete Python program: a Philippine Rice Retail System. But the real subject is abstraction β€” arguably the most important concept in all of programming β€” and understanding it deeply is what separates developers who write code from developers who architect systems.

Every code example in this guide is fully interactive β€” you can copy and run each snippet right in your browser using the Pyodide Python runtime. No setup required.

Complete source code Step-by-step explanation Live order form demo Cheat sheet & glossary Knowledge quiz

What is Abstraction, Really?

Imagine you are turning on a light switch. You do not need to understand electrical circuits, voltage transformers, or the power grid. The switch is the interface β€” it hides all of that complexity and gives you one simple action: flip. This is exactly what abstraction does in software.

The print_receipt() method is the light switch. The loops, lookups, and formatting logic inside it is the electrical grid β€” hidden away, managed internally, invisible to the caller. This is the architectural principle that makes large-scale software systems possible.

Definition: Abstraction is the practice of hiding implementation complexity behind clean, predictable interfaces. You define what an object can do, not how it does it.

Two-Class Architecture

The rice retail system uses exactly two classes β€” a minimalist design that demonstrates the Single Responsibility Principle in action.

  • RiceProduct β€” models a single rice variety with its name, price per kilo, variety type, and origin. This class is pure data representation.
  • RiceRetailSystem β€” manages the entire retail process: catalog, shopping cart, customer interaction, and receipt printing.

Why two classes? Separating data (RiceProduct) from behavior (RiceRetailSystem) follows the Single Responsibility Principle β€” each class has one clear purpose and can evolve independently.

The Complete rice_retail.py

This is the complete Python program. Copy it, run it, modify it β€” every part is explained below.

python
from abc import ABC, abstractmethod

class RiceProduct:
    """Represents a single rice product with name and price."""

    def __init__(self, name, price_per_kilo, variety, origin):
        self.name           = name
        self.price_per_kilo = price_per_kilo
        self.variety        = variety
        self.origin         = origin

    def display(self, code):
        print(f"  [{code}] {self.name:<28} β‚±{self.price_per_kilo:>6.2f}/kg  ({self.variety}, {self.origin})")


class RiceRetailSystem:
    """Manages the complete rice retail shop experience."""

    def __init__(self):
        self.catalog = {
            "R1": RiceProduct("Sinandomeng Premium",  58.00, "Indica",     "Nueva Ecija"),
            "R2": RiceProduct("Milagrosa Special",     62.00, "Aromatic",   "Batangas"),
            "R3": RiceProduct("Dinorado Heirloom",     75.00, "Heirloom",   "Mountain Province"),
            "R4": RiceProduct("NFA Standard Rice",      40.00, "Indica",     "NFA Warehouse"),
            "R5": RiceProduct("Jasmine Thai Import",    85.00, "Long Grain", "Imported"),
            "R6": RiceProduct("Brown Rice Organic",     95.00, "Whole Grain","Benguet"),
            "R7": RiceProduct("Glutinous Malagkit",    68.00, "Glutinous",  "Pampanga"),
            "R8": RiceProduct("Super Angelica Premium", 70.00, "Indica",     "Isabela"),
        }
        self.cart          = []
        self.customer_name = ""

    def show_banner(self):
        print("\n" + "═" * 60)
        print("       🌾  VALLEYS & BYTES RICE TRADING  🌾")
        print("          Premium Rice Retailer β€” Philippines")
        print("═" * 60)

    def show_catalog(self):
        print("\n  πŸ“¦  AVAILABLE RICE PRODUCTS (Price per Kilo):")
        print("  " + "─" * 56)
        for code, product in self.catalog.items():
            product.display(code)
        print("  " + "─" * 56)

    def get_customer_name(self):
        self.customer_name = input("\n  Enter your name: ").strip().title()
        print(f"\n  Welcome, {self.customer_name}! Let's build your order.")

    def add_to_cart(self):
        while True:
            print("\n  Enter product code (or 'DONE' to checkout, 'VIEW' to see cart):")
            code = input("  > ").strip().upper()
            if code == "DONE": break
            elif code == "VIEW": self.show_cart(); continue
            elif code not in self.catalog:
                print("  ❌ Invalid code. Please choose from the menu."); continue
            try:
                kilos = float(input(f"  How many kilos of {self.catalog[code].name}? "))
                if kilos <= 0:
                    print("  ⚠️  Enter a quantity greater than zero."); continue
                self.cart.append((code, kilos))
                print(f"  βœ… Added {kilos}kg β€” β‚±{self.catalog[code].price_per_kilo * kilos:.2f}")
            except ValueError:
                print("  ❌ Please enter a valid numeric quantity.")

    def show_cart(self):
        if not self.cart: print("  πŸ›’ Your cart is empty."); return
        print("\n  Current cart:")
        for i, (code, kilos) in enumerate(self.cart, 1):
            p = self.catalog[code]
            print(f"  {i}. {p.name} Γ— {kilos}kg = β‚±{p.price_per_kilo * kilos:.2f}")

    def calculate_total(self):
        return sum(self.catalog[c].price_per_kilo * k for c, k in self.cart)

    def print_receipt(self):
        if not self.cart: print("\n  πŸ›’ No items in cart."); return
        print("\n" + "═" * 60)
        print("          🧾  OFFICIAL RECEIPT  🧾")
        print("          Valleys & Bytes Rice Trading")
        print("═" * 60)
        print(f"  Customer : {self.customer_name}")
        print("  " + "─" * 56)
        print(f"  {'PRODUCT':<30} {'KG':>5}  {'SUBTOTAL':>10}")
        print("  " + "─" * 56)
        for code, kilos in self.cart:
            p = self.catalog[code]; subtotal = p.price_per_kilo * kilos
            print(f"  {p.name:<30} {kilos:>5.1f}  β‚±{subtotal:>9.2f}")
        print("  " + "─" * 56)
        print(f"  {'GRAND TOTAL':<36} β‚±{self.calculate_total():>9.2f}")
        print("═" * 60)
        print("  Salamat! Thank you for shopping at Valleys & Bytes! πŸ‡΅πŸ‡­")
        print("═" * 60 + "\n")

    def run(self):
        """Main entry point."""
        self.show_banner(); self.show_catalog()
        self.get_customer_name(); self.add_to_cart()
        self.print_receipt()


if __name__ == "__main__":
    shop = RiceRetailSystem()
    shop.run()

Code Explanation

Step 1: The RiceProduct Class

class RiceProduct:
    def __init__(self, name, price_per_kilo, variety, origin):
        self.name           = name
        self.price_per_kilo = price_per_kilo
        self.variety        = variety
        self.origin         = origin
πŸ“ Why This Code Was Used & How It Works
  1. Why a class for a single product? A class is a blueprint. Instead of four separate variables per product, one RiceProduct object bundles all related data β€” clean, organized, and reusable.
  2. Significance of __init__: The constructor runs automatically when RiceProduct("Sinandomeng", 58.0, ...) is called. self binds each argument to the new instance.
  3. Adding a new field: Adding stock_qty requires changing only this class. All other code continues working unchanged β€” true encapsulation.

Step 2: The Catalog Dictionary

self.catalog = {
    "R1": RiceProduct("Sinandomeng Premium", 58.00, "Indica", "Nueva Ecija"),
    "R2": RiceProduct("Milagrosa Special",   62.00, "Aromatic",   "Batangas"),
}
πŸ“ Why This Code Was Used & How It Works
  1. Why a dictionary? Dictionaries provide O(1) lookup. self.catalog["R3"] finds Dinorado instantly β€” no looping. The product code is the natural key for a retail system.
  2. Scaling up: If the catalog grows from 8 to 800 products, lookup speed stays constant. A list would require scanning every item β€” O(n) β€” which degrades with scale.
  3. Iteration: self.catalog.items() lets us iterate both keys and values together, making display and processing simple.

Step 3: While Loop & Shopping Cart

while True:
    code = input("  > ").strip().upper()
    if code == "DONE": break
    elif code not in self.catalog: continue
    self.cart.append((code, kilos))
πŸ“ Why This Code Was Used & How It Works
  1. Why while True? It's the correct construct for "keep accepting input until a stop condition." We don't know how many items the customer will order in advance.
  2. Input normalization: .strip().upper() ensures "r1", " R1 ", "R1" all resolve correctly.
  3. Cart structure: self.cart.append((code, kilos)) adds a tuple to the cart list. Tuples are used because each cart entry is an immutable (code, quantity) pair.

Step 4: Try/Except & Calculate Total

try:
    kilos = float(input("How many kilos? "))
except ValueError:
    print("❌ Please enter a valid number.")

def calculate_total(self):
    return sum(self.catalog[c].price_per_kilo * k for c, k in self.cart)
πŸ“ Why This Code Was Used & How It Works
  1. Why try/except? float("banana") raises ValueError. Without try/except the program crashes. With it, bad input is handled gracefully β€” critical for robustness.
  2. Generator expression: The generator in calculate_total() is memory-efficient β€” it computes values on-the-fly without building an intermediate list.
  3. How it operates: For each (code, kilos) pair in the cart, it looks up the price and multiplies. Results stream directly into sum() β€” concise, Pythonic, and fast.

πŸ’‘ f-string formatting: f"{self.name:<28}" left-aligns in a 28-character field. f"β‚±{price:>6.2f}" right-aligns with 2 decimal places. This produces perfectly columnar console output β€” the same technique used in professional CLI tools.

Python OOP Concepts Reference

ConceptWhat It IsHow It's Used Here
classBlueprint for creating objectsDefines RiceProduct and RiceRetailSystem
__init__(self)Constructor β€” runs on object creationInitializes product attributes and empty cart
selfReference to current instanceAllows methods to access the object's own data
dict {}Key-value store with O(1) lookupProduct catalog: code β†’ RiceProduct
list []Ordered, mutable sequenceShopping cart: list of (code, kilos) tuples
for…inIteration over a collectionDisplay catalog, generate receipt lines
while TrueInfinite loop with break/continueShopping session alive until "DONE"
try/exceptCatches runtime errors gracefullyHandles non-numeric kilo inputs
f-stringInline variable embedding + formattingAligned product listings and receipt output
sum() + generatorReduces iterable to single valueGrand total from all cart items
.strip().upper()String normalization methods"r1", " R1 ", "R1 " all become "R1"
enumerate()Adds index counter to iterationNumbers cart items in cart display
if __name__=="__main__"Module guard β€” direct execution onlySafe entry point; importable elsewhere

Interactive Order Form

Interactive implementation of the Python program β€” select a variety, enter quantity, and receive an instant receipt.

Valleys & Bytes Rice Trading β€” Encode Your Order
Total Cost
β€”
β‚±0.00

⚠ Please select a variety and enter a valid quantity.

βœ… Official Receipt β€” Valleys & Bytes Rice Trading

Productβ€”
Variety / Originβ€”
Unit Priceβ€”
Quantityβ€”
Total Costβ€”

Salamat! Thank you for shopping β€” Valleys & Bytes Rice Trading πŸ‡΅πŸ‡­

OOP Cheat Sheet

Everything you need in one place β€” bookmark this and refer back whenever you need a reminder.

🧩 Class Syntax

SyntaxPurpose
class Foo:Define a class
class Bar(Foo):Inherit from Foo
def __init__(self)Constructor
self.attr = valInstance attribute
def __str__(self)String representation

πŸ“¦ Rice Prices

VarietyPrice/kg
Sinandomengβ‚±58.00
Milagrosaβ‚±62.00
Dinoradoβ‚±75.00
NFA Standardβ‚±40.00
Brown Organicβ‚±95.00

πŸ”‘ Dictionary Patterns

PatternPurpose
d = {k: v}Create dict
d[key]O(1) lookup
d.items()Iterate key+value
key in dMembership test
d.get(k, default)Safe lookup

⚑ Common Patterns

PatternPurpose
if __name__=="__main__"Entry point
try / except ValueErrorCatch bad input
sum(x for x in lst)Generator total
.strip().upper()Normalize input
list.append(tuple)Add to cart

Key Terms

classA blueprint that defines attributes and methods. RiceProduct is the blueprint; RiceProduct("Sinandomeng", 58.0, ...) is an instance created from that blueprint.
__init__Constructor β€” called automatically when creating an object. Sets the initial state by assigning values to instance attributes via self.
selfReference to the current instance. Every method receives self as its first argument to access and modify that specific object's data independently.
abstractionHiding internal complexity behind a clean interface. Calling print_receipt() hides all the loop and formatting logic inside the method body.
dictionaryA hash-table data structure that maps keys to values. catalog["R1"] returns the Sinandomeng product in O(1) time regardless of catalog size.
f-stringFormatted string literal. f"β‚±{price:.2f}" embeds variables directly with format specifiers β€” always exactly 2 decimal places here.
try/exceptError-handling block. Code in try runs normally; if a specific error occurs, except catches it instead of crashing the program.
generator expressionA lazy iterator that computes values on demand: sum(p * k for c, k in cart). More memory-efficient than a list comprehension for large datasets.
encapsulationBundling data and the methods that operate on it inside a class. External code accesses product data through the object's interface, not directly.

Knowledge Quiz

Reinforce what you've learned. Each question targets a core concept from this guide.

0/5

Great effort β€” review any missed questions above.

Python OOP Learning Roadmap

1

Classes & Instances (You Are Here)

Master __init__, self, instance vs class attributes. Build 5–10 simple classes β€” products, students, animals β€” from scratch without inheritance.

2

Encapsulation β€” Properties & Validation

Learn @property, @x.setter, protected (_attr) vs private (__attr) attributes. Add validated price setters that reject negative values.

3

Inheritance β€” Subclasses & super()

Create subclasses like PremiumRice(RiceProduct) that add discount logic. Understand method resolution order (MRO) and when to call super().__init__().

4

Magic Methods β€” Dunder Protocol

Go beyond __init__. Learn __repr__, __eq__, __lt__, __add__ to make your classes behave like built-in Python types.

5

Abstract Classes & Interfaces

Use abc.ABC and @abstractmethod to enforce method implementation in subclasses. Design plugin systems and extensible APIs.

6

Frameworks β€” Django, FastAPI, SQLAlchemy

Apply OOP knowledge to production frameworks. Django models use the same class-based design. Pydantic models in FastAPI use @validator β€” your property pattern, hardened for scale.

🎯 Next step: Extend this rice system β€” add a Discount class using composition, a PremiumRice subclass with loyalty pricing, and a report_summary() method. Each addition deepens your OOP instincts significantly.

Spread the Knowledge

Subscribe to Poetic Bytes

Get New Python Tutorials Delivered

Join 8,400+ developers getting weekly Python OOP walkthroughs, code breakdowns, and real-world project tutorials β€” straight to your inbox. No spam, ever.

Weekly Python tutorials OOP project walkthroughs Free cheat sheets No spam, ever

πŸ”’ Join 8,400+ developers. Your email is never shared or sold.

Follow Me

Stay connected for more Python tutorials, coding tips, and Filipino developer content.