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.
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.
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.
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 a class for a single product? A class is a blueprint. Instead of four separate variables per product, one
RiceProductobject bundles all related data β clean, organized, and reusable. -
Significance of
__init__: The constructor runs automatically whenRiceProduct("Sinandomeng", 58.0, ...)is called.selfbinds each argument to the new instance. -
Adding a new field: Adding
stock_qtyrequires 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 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. - 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.
-
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
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. -
Input normalization:
.strip().upper()ensures "r1", " R1 ", "R1" all resolve correctly. -
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 try/except?
float("banana")raisesValueError. Without try/except the program crashes. With it, bad input is handled gracefully β critical for robustness. -
Generator expression: The generator in
calculate_total()is memory-efficient β it computes values on-the-fly without building an intermediate list. -
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
| Concept | What It Is | How It's Used Here |
|---|---|---|
class | Blueprint for creating objects | Defines RiceProduct and RiceRetailSystem |
__init__(self) | Constructor β runs on object creation | Initializes product attributes and empty cart |
self | Reference to current instance | Allows methods to access the object's own data |
dict {} | Key-value store with O(1) lookup | Product catalog: code β RiceProduct |
list [] | Ordered, mutable sequence | Shopping cart: list of (code, kilos) tuples |
forβ¦in | Iteration over a collection | Display catalog, generate receipt lines |
while True | Infinite loop with break/continue | Shopping session alive until "DONE" |
try/except | Catches runtime errors gracefully | Handles non-numeric kilo inputs |
f-string | Inline variable embedding + formatting | Aligned product listings and receipt output |
sum() + generator | Reduces iterable to single value | Grand total from all cart items |
.strip().upper() | String normalization methods | "r1", " R1 ", "R1 " all become "R1" |
enumerate() | Adds index counter to iteration | Numbers cart items in cart display |
if __name__=="__main__" | Module guard β direct execution only | Safe entry point; importable elsewhere |
Interactive Order Form
Interactive implementation of the Python program β select a variety, enter quantity, and receive an instant receipt.
β Please select a variety and enter a valid quantity.
β Official Receipt β Valleys & Bytes Rice Trading
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
| Syntax | Purpose |
|---|---|
class Foo: | Define a class |
class Bar(Foo): | Inherit from Foo |
def __init__(self) | Constructor |
self.attr = val | Instance attribute |
def __str__(self) | String representation |
π¦ Rice Prices
| Variety | Price/kg |
|---|---|
| Sinandomeng | β±58.00 |
| Milagrosa | β±62.00 |
| Dinorado | β±75.00 |
| NFA Standard | β±40.00 |
| Brown Organic | β±95.00 |
π Dictionary Patterns
| Pattern | Purpose |
|---|---|
d = {k: v} | Create dict |
d[key] | O(1) lookup |
d.items() | Iterate key+value |
key in d | Membership test |
d.get(k, default) | Safe lookup |
β‘ Common Patterns
| Pattern | Purpose |
|---|---|
if __name__=="__main__" | Entry point |
try / except ValueError | Catch bad input |
sum(x for x in lst) | Generator total |
.strip().upper() | Normalize input |
list.append(tuple) | Add to cart |
Key Terms
RiceProduct is the blueprint; RiceProduct("Sinandomeng", 58.0, ...) is an instance created from that blueprint.self.self as its first argument to access and modify that specific object's data independently.print_receipt() hides all the loop and formatting logic inside the method body.catalog["R1"] returns the Sinandomeng product in O(1) time regardless of catalog size.f"β±{price:.2f}" embeds variables directly with format specifiers β always exactly 2 decimal places here.try runs normally; if a specific error occurs, except catches it instead of crashing the program.sum(p * k for c, k in cart). More memory-efficient than a list comprehension for large datasets.Knowledge Quiz
Reinforce what you've learned. Each question targets a core concept from this guide.
Great effort β review any missed questions above.
Python OOP Learning Roadmap
Classes & Instances (You Are Here)
Master __init__, self, instance vs class attributes. Build 5β10 simple classes β products, students, animals β from scratch without inheritance.
Encapsulation β Properties & Validation
Learn @property, @x.setter, protected (_attr) vs private (__attr) attributes. Add validated price setters that reject negative values.
Inheritance β Subclasses & super()
Create subclasses like PremiumRice(RiceProduct) that add discount logic. Understand method resolution order (MRO) and when to call super().__init__().
Magic Methods β Dunder Protocol
Go beyond __init__. Learn __repr__, __eq__, __lt__, __add__ to make your classes behave like built-in Python types.
Abstract Classes & Interfaces
Use abc.ABC and @abstractmethod to enforce method implementation in subclasses. Design plugin systems and extensible APIs.
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.
Explore More OOP Projects
Every app below is built with Python OOP at its core. Click to explore a live implementation.
A full-stack abstract commerce engine. Product catalogue, cart, and checkout behind a unified ABC interface.
πͺ Commerce OOPAn inventory system where each fruit type is a concrete class extending an abstract Produce base.
πΏ Inventory OOPWine selection and pricing abstracted behind a Beverage ABC β swap vintages without changing business logic.
πΎ Catalogue OOPGrain variety management with abstract stock controls and validated pricing through property decorators.
π¦ Retail OOPCommodity trading simulation with abstract market pricing and encapsulated transaction records.
βοΈ Commodity OOPExplore the Full OOP Series
Abstraction is the second of four interconnected pillars. Each one builds on the last. Explore the complete journey.
Bundle data with methods. Control access. Protect state. The guardian of object integrity.
Explore βHide implementation complexity. Expose only what is necessary. Think in concepts, not mechanics.
Explore βBuild on existing classes. Reuse code elegantly. Create natural type hierarchies.
Explore βOne interface, many forms. Duck typing. Write code that adapts to any conforming object.
Explore β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.
π 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.