Learning Python Through
Copra & Rubber β
A Complete Walkthrough
A step-by-step journey through Python OOP grounded in a real Philippine copra and rubber trading post. Learn true abstraction with abc.ABC, dual buy/sell pricing, inheritance, dictionaries, and f-strings all in one cohesive program.
Why Copra & Rubber, Why Python?
π₯₯ Building a Philippine Copra & Rubber Trading Post in Python
Copra and rubber are generational livelihoods across Quezon, Davao, Cotabato, and Basilan. A trading post β locally a bagsakan β buys raw copra and rubber from farmers, then sells it onward to processors and exporters at a higher price. That buy-sell spread is the business.
This tutorial is built around one complete Python program, but the real subject is formal abstraction β using Python's built-in abc module to define an interface that the language itself enforces, not just a convention developers agree to follow.
Every code example here is fully interactive β copy or run each snippet right in your browser using the Pyodide Python runtime. No setup required.
From Informal to Formal Abstraction
The rice.html tutorial hid complexity inside a method β informal abstraction. Here, we go further: TradableProduct(ABC) defines a contract with @abstractmethod that every subclass must fulfill. Forget to implement buy_price and Python refuses to create the object β a TypeError, not a silent bug found later.
Definition: Formal abstraction means defining an interface that the language enforces at runtime β not just naming a function and hoping callers respect the intent behind it.
Three-Class Architecture
The trading post uses an abstract base plus two concrete product types β a minimal hierarchy that shows inheritance and formal contracts working together.
TradableProductβ the abstract base. Cannot be instantiated. Requiresbuy_priceandsell_price.CopraProduct/RubberProductβ concrete subclasses with commodity-specific data.CopraRubberTradingPostβ orchestrates the buy/sell workflow and receipts.
Why an abstract base? It guarantees every product β present or future β exposes both prices, letting the receipt engine trust the interface without knowing the concrete type.
The Complete copra_rubber_trading.py
This is the complete Python program. Copy it, run it, modify it β every part is explained below.
from abc import ABC, abstractmethod from datetime import datetime class TradableProduct(ABC): """Abstract base -- enforces buy_price, sell_price, and display().""" def __init__(self, code, name, unit, origin, grade): self.code = code self.name = name self.unit = unit self.origin = origin self.grade = grade @property @abstractmethod def buy_price(self): ... @property @abstractmethod def sell_price(self): ... @abstractmethod def display(self): ... def margin(self): return self.sell_price - self.buy_price class CopraProduct(TradableProduct): def __init__(self, code, name, origin, grade, buy, sell): super().__init__(code, name, "kg", origin, grade) self._buy, self._sell = buy, sell @property def buy_price(self): return self._buy @property def sell_price(self): return self._sell def display(self): print(f" [{self.code}] {self.name:<28} | Grade: {self.grade:<8} | " f"Buy: β±{self._buy:>7.2f}/kg Sell: β±{self._sell:>7.2f}/kg") class RubberProduct(TradableProduct): def __init__(self, code, name, origin, grade, buy, sell, rubber_type): super().__init__(code, name, "kg", origin, grade) self._buy, self._sell = buy, sell self.rubber_type = rubber_type @property def buy_price(self): return self._buy @property def sell_price(self): return self._sell def display(self): print(f" [{self.code}] {self.name:<28} | Type: {self.rubber_type:<10} | " f"Buy: β±{self._buy:>7.2f}/kg Sell: β±{self._sell:>7.2f}/kg") class CopraRubberTradingPost: def __init__(self): self.catalog = { "C1": CopraProduct("C1", "Sun-Dried Copra", "Quezon", "Grade A", 28.00, 32.00), "C2": CopraProduct("C2", "Kiln-Dried Copra", "Davao", "Grade A", 30.00, 35.00), "C3": CopraProduct("C3", "Ball Copra", "Laguna", "Grade B", 24.00, 28.00), "R1": RubberProduct("R1", "Cup Lump Standard", "Cotabato", "Grade 1", 52.00, 58.00, "Cup Lump"), "R2": RubberProduct("R2", "RSS-1 Smoked Sheet", "Zamboanga", "RSS-1", 68.00, 75.00, "RSS"), } self.transaction_log = [] def show_catalog(self): print("\n π¦ CURRENT PRICE LIST:") for code, p in self.catalog.items(): p.display() def _encode_items(self, mode): # mode = 'buy' (we pay a seller) or 'sell' (we charge a customer) items = [] while True: code = input("Enter code (DONE to finish): ").strip().upper() if code == "DONE": break if code not in self.catalog: print("β Invalid code."); continue try: qty = float(input(f"Quantity in kg for {self.catalog[code].name}? ")) price = self.catalog[code].buy_price if mode == "buy" else self.catalog[code].sell_price items.append((code, qty, price, price * qty)) print(f"β {qty}kg Γ β±{price:.2f} = β±{price*qty:.2f}") except ValueError: print("β Invalid quantity.") return items def print_transaction(self, party, mode, items): if not items: print("π No items encoded."); return total = sum(s for _, _, _, s in items) label = "BUYING RECEIPT" if mode == "buy" else "SELLING RECEIPT" print(f"\n{label} β {party} β {datetime.now():%b %d, %Y}") for code, qty, price, sub in items: p = self.catalog[code] print(f" {p.name:<28} {qty:>6.1f}kg β±{sub:>9.2f}") print(f" {'TOTAL':<28} {'':>6} β±{total:>9.2f}") self.transaction_log.append({"party": party, "mode": mode, "total": total}) def run(self): self.show_catalog() choice = input("\n[1] BUY from seller [2] SELL to customer: ").strip() mode = "buy" if choice == "1" else "sell" party = input("Name: ").strip().title() items = self._encode_items(mode) self.print_transaction(party, mode, items) if __name__ == "__main__": post = CopraRubberTradingPost() post.run()
Code Explanation
Step 1: Abstract Base Class
class TradableProduct(ABC): @property @abstractmethod def buy_price(self): ... @property @abstractmethod def sell_price(self): ...
- Why
ABC? Inheriting fromABCmakesTradableProductuninstantiable on its own.TradableProduct()raisesTypeErrorβ it only exists to be subclassed. - Why
@abstractmethod? Any subclass that forgets to implementbuy_priceorsell_pricecan't be instantiated either β the contract is enforced by the interpreter, not documentation. - Why stack it with
@property? It forces subclasses to expose the value as an attribute βproduct.buy_price, notproduct.buy_price()β a uniform interface across every product type.
Step 2: Subclasses & super().__init__()
class CopraProduct(TradableProduct): def __init__(self, code, name, origin, grade, buy, sell): super().__init__(code, name, "kg", origin, grade) self._buy, self._sell = buy, sell
- Why
super().__init__()? It delegates the five shared fields to the parent constructor. The child only sets its own data β no duplicated initialization logic betweenCopraProductandRubberProduct. - Why the underscore in
_buy? A leading underscore signals "internal use" by convention. External code readsproduct.buy_pricethrough the property, not the raw attribute. - Adding a new commodity: A future
CharcoalProduct(TradableProduct)only needs its own__init__and the two required properties β the rest of the system supports it automatically.
Step 3: One Method, Two Modes
def _encode_items(self, mode): price = self.catalog[code].buy_price if mode == "buy" else self.catalog[code].sell_price items.append((code, qty, price, price * qty))
- Why one method for both modes? Buying and selling only differ in which price applies. A single
modeparameter avoids duplicating the entire input loop β DRY (Don't Repeat Yourself). - The ternary expression:
a if cond else bis Python's inline conditional β a compact, readable way to pick between two values. - Tuple storage: Each encoded item is a
(code, qty, price, subtotal)tuple β an immutable snapshot of a completed calculation, ready for the receipt.
Step 4: Generator Total & Transaction Log
total = sum(s for _, _, _, s in items) self.transaction_log.append({"party": party, "mode": mode, "total": total})
- Generator expression:
sum(s for _,_,_,s in items)unpacks each tuple and streams the subtotal straight intosum()β memory-efficient and Pythonic. - Why log transactions? Appending a summary dict after every receipt builds a running record of the day's business β the seed of a real reporting feature.
π‘ margin() on the base class: TradableProduct.margin() returns sell_price - buy_price as a concrete method. Because both abstract properties are guaranteed on every subclass, this calculation is safe to write once β shared logic belongs on the parent, specific data belongs on the child.
Python OOP Concepts Reference
| Concept | What It Is | How It's Used Here |
|---|---|---|
abc.ABC | Base class that blocks direct instantiation | Parent of TradableProduct |
@abstractmethod | Forces subclasses to implement a method | buy_price, sell_price, display |
@property | Method accessed like an attribute | product.buy_price not product.buy_price() |
super().__init__() | Calls the parent constructor | Shares field setup between subclasses |
class X(Parent): | Inheritance β child extends parent | CopraProduct(TradableProduct) |
isinstance() | Checks object class membership | Groups copra vs rubber for display |
| ternary expr | Inline conditional: a if cond else b | Selects buy or sell price by mode |
dict {} | Key-value store with O(1) lookup | Product catalog: code β product object |
tuple (a,b,c) | Immutable ordered collection | Transaction items: (code, qty, price, subtotal) |
sum() + generator | Reduces an iterable to one value | Grand total from all transaction items |
try/except | Catches runtime errors gracefully | Handles non-numeric quantity input |
datetime.now() | Current timestamp | Stamps every receipt |
if __name__=="__main__" | Module guard β direct execution only | Safe entry point; importable elsewhere |
Current Buy & Sell Prices
Green is what the trading post pays a seller. Blue is what it charges a buyer. The gap between them is the margin.
Interactive Buy / Sell Form
Interactive implementation of the Python program β choose a mode, select a product, enter a quantity, and receive an instant receipt with the correct price applied.
β Please select a product and enter a valid quantity.
π’ Buying Receipt β Valleys & Bytes Trading Post
Salamat! Transaction recorded β Valleys & Bytes Trading Post π΅π
β Please select a product and enter a valid quantity.
π΅ Selling Receipt β Valleys & Bytes Trading Post
Maraming salamat! β Valleys & Bytes Trading Post π΅π
OOP & Abstraction Cheat Sheet
Everything you need in one place β bookmark this and refer back whenever you need a reminder.
π§© Abstract Class Syntax
| Syntax | Purpose |
|---|---|
class X(ABC): | Define abstract class |
@abstractmethod | Must be implemented |
@property + @abstractmethod | Abstract property |
super().__init__() | Call parent init |
isinstance(obj, Cls) | Type check |
π¦ Buy Prices
| Product | Buy/kg |
|---|---|
| Sun-Dried Copra | β±28.00 |
| Kiln-Dried Copra | β±30.00 |
| Cup Lump Standard | β±52.00 |
| RSS-1 Sheet | β±68.00 |
| TSR-20 Rubber | β±72.00 |
π° Sell Prices
| Product | Sell/kg |
|---|---|
| Sun-Dried Copra | β±32.00 |
| Kiln-Dried Copra | β±35.00 |
| Cup Lump Standard | β±58.00 |
| RSS-1 Sheet | β±75.00 |
| TSR-20 Rubber | β±80.00 |
β‘ Common Patterns
| Pattern | Purpose |
|---|---|
if __name__=="__main__" | Entry point |
try / except ValueError | Catch bad input |
sum(x for x in lst) | Generator total |
a if cond else b | Ternary |
self._attr | Private by convention |
Key Terms
TradableProduct() raises TypeError. It exists only to be subclassed.product.buy_price looks like plain data access but runs a method behind the scenes.True if an object belongs to a class or its subclasses. Used to separate copra from rubber when both live in one dictionary.sell_price - buy_price. Safe to define once because both properties are guaranteed on every subclass.f"β±{price:.2f}" embeds variables with format specifiers directly in the string.sum(s for _,_,_,s in items). More memory-efficient than building a full list first.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 (Rice Tutorial)
Master __init__, self, and simple product classes without inheritance. Start here if abstract base classes feel unfamiliar.
Abstract Classes & Interfaces (You Are Here)
Use abc.ABC and @abstractmethod to enforce contracts. Learn @property, super().__init__(), and isinstance() filtering across a small inheritance tree.
Encapsulation β Setters & Validation
Add @price.setter methods that reject negative prices or invalid grades β real data protection, not just naming convention.
Magic Methods β Dunder Protocol
Add __repr__, __eq__, __lt__ so products sort by margin and print cleanly in debuggers.
Data Classes & Type Hints
Refactor with @dataclass and full type hints. Swap float for Decimal where financial precision matters.
Frameworks β Django, FastAPI, SQLAlchemy
TradableProduct maps directly onto a Django abstract model. The abstract-base pattern is the backbone of every serious Python framework.
π― Next step: Add a PremiumCopra(CopraProduct) subclass with loyalty pricing, a TradingSession class that totals daily profit, and a CSV export for the transaction log. Each extension adds behavior without touching existing classes.
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.