Python OOP Β· Philippine Copra & Rubber Trading

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.

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

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.

Complete source code Step-by-step explanation Live buy/sell demo Cheat sheet & glossary Knowledge quiz

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. Requires buy_price and sell_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.

python
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 This Code Was Used & How It Works
  1. Why ABC? Inheriting from ABC makes TradableProduct uninstantiable on its own. TradableProduct() raises TypeError β€” it only exists to be subclassed.
  2. Why @abstractmethod? Any subclass that forgets to implement buy_price or sell_price can't be instantiated either β€” the contract is enforced by the interpreter, not documentation.
  3. Why stack it with @property? It forces subclasses to expose the value as an attribute β€” product.buy_price, not product.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 This Code Was Used & How It Works
  1. Why super().__init__()? It delegates the five shared fields to the parent constructor. The child only sets its own data β€” no duplicated initialization logic between CopraProduct and RubberProduct.
  2. Why the underscore in _buy? A leading underscore signals "internal use" by convention. External code reads product.buy_price through the property, not the raw attribute.
  3. 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 This Code Was Used & How It Works
  1. Why one method for both modes? Buying and selling only differ in which price applies. A single mode parameter avoids duplicating the entire input loop β€” DRY (Don't Repeat Yourself).
  2. The ternary expression: a if cond else b is Python's inline conditional β€” a compact, readable way to pick between two values.
  3. 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})
πŸ“ Why This Code Was Used & How It Works
  1. Generator expression: sum(s for _,_,_,s in items) unpacks each tuple and streams the subtotal straight into sum() β€” memory-efficient and Pythonic.
  2. 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

ConceptWhat It IsHow It's Used Here
abc.ABCBase class that blocks direct instantiationParent of TradableProduct
@abstractmethodForces subclasses to implement a methodbuy_price, sell_price, display
@propertyMethod accessed like an attributeproduct.buy_price not product.buy_price()
super().__init__()Calls the parent constructorShares field setup between subclasses
class X(Parent):Inheritance β€” child extends parentCopraProduct(TradableProduct)
isinstance()Checks object class membershipGroups copra vs rubber for display
ternary exprInline conditional: a if cond else bSelects buy or sell price by mode
dict {}Key-value store with O(1) lookupProduct catalog: code β†’ product object
tuple (a,b,c)Immutable ordered collectionTransaction items: (code, qty, price, subtotal)
sum() + generatorReduces an iterable to one valueGrand total from all transaction items
try/exceptCatches runtime errors gracefullyHandles non-numeric quantity input
datetime.now()Current timestampStamps every receipt
if __name__=="__main__"Module guard β€” direct execution onlySafe 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.

πŸ₯₯ Copra β€” per kilogram
C1 β€” Sun-Dried Copra (Grade A)
β‚±28.00β‚±32.00
C2 β€” Kiln-Dried Copra (Grade A)
β‚±30.00β‚±35.00
C3 β€” Ball Copra (Grade B)
β‚±24.00β‚±28.00
C4 β€” Split Copra (Grade B)
β‚±22.50β‚±26.50
C5 β€” Whole Copra Premium
β‚±33.00β‚±38.00
🌿 Rubber β€” per kilogram
R1 β€” Cup Lump Standard
β‚±52.00β‚±58.00
R2 β€” Cup Lump Premium
β‚±55.00β‚±62.00
R3 β€” RSS-1 Smoked Sheet
β‚±68.00β‚±75.00
R4 β€” RSS-3 Smoked Sheet
β‚±60.00β‚±67.00
R5 β€” TSR-20 Spec Rubber
β‚±72.00β‚±80.00

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.

Buying Mode β€” Encode Seller's Delivery
Total to Pay Seller
β€”
β‚±0.00

⚠ Please select a product and enter a valid quantity.

🟒 Buying Receipt β€” Valleys & Bytes Trading Post

Productβ€”
Grade / Originβ€”
Buy Priceβ€”
Quantityβ€”
Amount to Pay Sellerβ€”

Salamat! Transaction recorded β€” Valleys & Bytes Trading Post πŸ‡΅πŸ‡­

Selling Mode β€” Encode Customer Order
Total to Collect
β€”
β‚±0.00

⚠ Please select a product and enter a valid quantity.

πŸ”΅ Selling Receipt β€” Valleys & Bytes Trading Post

Productβ€”
Grade / Originβ€”
Sell Priceβ€”
Quantityβ€”
Amount to Collectβ€”

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

SyntaxPurpose
class X(ABC):Define abstract class
@abstractmethodMust be implemented
@property + @abstractmethodAbstract property
super().__init__()Call parent init
isinstance(obj, Cls)Type check

πŸ“¦ Buy Prices

ProductBuy/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

ProductSell/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

PatternPurpose
if __name__=="__main__"Entry point
try / except ValueErrorCatch bad input
sum(x for x in lst)Generator total
a if cond else bTernary
self._attrPrivate by convention

Key Terms

abc.ABCAbstract Base Class. Inheriting from it blocks direct instantiation β€” TradableProduct() raises TypeError. It exists only to be subclassed.
@abstractmethodMarks a method as required in any concrete subclass. Omitting it prevents that subclass from being instantiated at all.
@propertyConverts a method into a read-only attribute. product.buy_price looks like plain data access but runs a method behind the scenes.
super().__init__()Calls the parent class's constructor so shared attributes are set before the subclass adds its own.
isinstance()Returns True if an object belongs to a class or its subclasses. Used to separate copra from rubber when both live in one dictionary.
margin()A concrete method on the abstract base: sell_price - buy_price. Safe to define once because both properties are guaranteed on every subclass.
f-stringFormatted string literal. f"β‚±{price:.2f}" embeds variables with format specifiers directly in the string.
generator expressionA lazy iterator that computes values on demand: sum(s for _,_,_,s in items). More memory-efficient than building a full list first.
CopraDried coconut meat, the primary raw material for coconut oil. Graded by drying method β€” sun-dried, kiln-dried, ball, or split.
Cup Lump / RSSTwo natural rubber grades. Cup Lump forms by coagulation in collection cups; Ribbed Smoked Sheet (RSS) is rolled and smoked for higher quality.

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 (Rice Tutorial)

Master __init__, self, and simple product classes without inheritance. Start here if abstract base classes feel unfamiliar.

2

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.

3

Encapsulation β€” Setters & Validation

Add @price.setter methods that reject negative prices or invalid grades β€” real data protection, not just naming convention.

4

Magic Methods β€” Dunder Protocol

Add __repr__, __eq__, __lt__ so products sort by margin and print cleanly in debuggers.

5

Data Classes & Type Hints

Refactor with @dataclass and full type hints. Swap float for Decimal where financial precision matters.

6

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.

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.