πŸ₯₯
VALLEYS & BYTES
Copra Β· Rubber Β· Python OOP
← Return to Abstraction Hub

Python OOP Β· Abstraction Β· Philippine Copra & Rubber Trading

Mastering Python Through
Copra & Rubber Trading β€”
A Complete Walkthrough

A step-by-step journey through Python OOP grounded in a real Philippine copra and rubber trading business. Learn abstraction, dual buy/sell pricing, classes, dictionaries, loops, f-strings, and error handling in one cohesive program.

Python 3.x OOP Β· Abstraction Β· Classes Buy & Sell Logic Dictionaries Β· Loops Β· f-strings Beginner β†’ Intermediate πŸ‡΅πŸ‡­ Philippines
copra_rubber_trading.py β€” Full Source
# ============================================================
# VALLEYS & BYTES β€” Philippine Copra & Rubber Trading System
# Python Abstraction Β· OOP Β· Dual Buy/Sell Pricing
# ============================================================

from abc import ABC, abstractmethod
from datetime import datetime


# ── Abstract Base Product ─────────────────────────────────
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        # "kg" or "slab"
        self.origin     = origin
        self.grade      = grade

    @property
    @abstractmethod
    def buy_price(self):
        """Price the trading post PAYS the seller (per unit)."""

    @property
    @abstractmethod
    def sell_price(self):
        """Price the trading post CHARGES the buyer (per unit)."""

    @abstractmethod
    def display(self):
        """Print product details with buy and sell prices."""

    def margin(self):
        return self.sell_price - self.buy_price


# ── Copra Product ─────────────────────────────────────────
class CopraProduct(TradableProduct):
    """Dried coconut meat β€” primary copra trade product."""

    def __init__(self, code, name, origin, grade, buy, sell):
        super().__init__(code, name, "kg", origin, grade)
        self._buy  = buy
        self._sell = 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")


# ── Rubber Product ────────────────────────────────────────
class RubberProduct(TradableProduct):
    """Natural rubber β€” cup lump and ribbed smoked sheet."""

    def __init__(self, code, name, origin, grade, buy, sell, rubber_type):
        super().__init__(code, name, "kg", origin, grade)
        self._buy         = buy
        self._sell        = sell
        self.rubber_type  = rubber_type  # "Cup Lump" or "RSS"

    @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")


# ── Trading Post (main system) ────────────────────────────
class CopraRubberTradingPost:
    """Manages the full buy/sell workflow for copra and rubber."""

    def __init__(self):
        self.catalog = {
            # ── Copra Products ──────────────────────────────
            "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),
            "C4": CopraProduct("C4", "Split Copra",           "Zamboanga",   "Grade B", 22.50, 26.50),
            "C5": CopraProduct("C5", "Whole Copra Premium",   "Quezon",      "Premium", 33.00, 38.00),
            # ── Rubber Products ─────────────────────────────
            "R1": RubberProduct("R1", "Cup Lump Standard",     "Cotabato",    "Grade 1", 52.00, 58.00, "Cup Lump"),
            "R2": RubberProduct("R2", "Cup Lump Premium",      "Basilan",     "Grade 1", 55.00, 62.00, "Cup Lump"),
            "R3": RubberProduct("R3", "RSS-1 Smoked Sheet",    "Zamboanga",   "RSS-1",   68.00, 75.00, "RSS"),
            "R4": RubberProduct("R4", "RSS-3 Smoked Sheet",    "Cotabato",    "RSS-3",   60.00, 67.00, "RSS"),
            "R5": RubberProduct("R5", "Technically Spec Rubber", "Davao",      "TSR-20",  72.00, 80.00, "TSR"),
        }
        self.transaction_log = []

    def show_banner(self):
        print("═" * 65)
        print("     🍇 VALLEYS & BYTES β€” Copra & Rubber Trading Post")
        print("             Philippines Β· Est. 2025")
        print("═" * 65)

    def show_catalog(self):
        print("\n  📦  CURRENT PRICE LIST:")
        print("  ─" * 33)
        print("  COPRA PRODUCTS:")
        for code, p in self.catalog.items():
            if isinstance(p, CopraProduct): p.display()
        print("  RUBBER PRODUCTS:")
        for code, p in self.catalog.items():
            if isinstance(p, RubberProduct): p.display()
        print("  ─" * 33)

    def _encode_items(self, mode):
        """mode = 'buy' (we buy from seller) or 'sell' (we sell to customer)."""
        items = []
        while True:
            code = input(f"\n  Enter product code (or 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}? "))
                if qty <= 0: print("  ⚠ Enter a positive quantity."); continue
                price = self.catalog[code].buy_price if mode == "buy" else self.catalog[code].sell_price
                subtotal = price * qty
                items.append((code, qty, price, subtotal))
                print(f"  ✅ {qty}kg × ₱{price:.2f} = ₱{subtotal:.2f}")
            except ValueError:
                print("  ❌ Invalid quantity. Enter a number.")
        return items

    def print_transaction(self, party, mode, items):
        if not items: print("\n  🛒 No items encoded."); return
        label = "BUYING RECEIPT" if mode == "buy" else "SELLING RECEIPT"
        note  = "Amount to PAY seller" if mode == "buy" else "Amount to COLLECT"
        total = sum(s for _, _, _, s in items)
        print(f"\n{'═'*65}")
        print(f"  🍇  {label} β€” Valleys & Bytes Trading Post")
        print(f"  Party : {party}   Date: {datetime.now().strftime('%b %d, %Y %I:%M %p')}")
        print("═" * 65)
        print(f"  {'PRODUCT':<30} {'KG':>6}  {'PRICE/kg':>10}  {'SUBTOTAL':>11}")
        print("  ─" * 33)
        for code, qty, price, sub in items:
            p = self.catalog[code]
            print(f"  {p.name:<30} {qty:>6.1f}  ₱{price:>9.2f}  ₱{sub:>10.2f}")
        print("  ─" * 33)
        print(f"  {note:<39} ₱{total:>10.2f}")
        print("═" * 65 + "\n")
        self.transaction_log.append({"party": party, "mode": mode, "total": total})

    def run_buy_session(self):
        """Buying mode: the trading post buys from a seller."""
        print("\n  🟢 BUYING MODE β€” You are purchasing from a seller.")
        seller = input("  Seller's name: ").strip().title()
        self.show_catalog()
        items = self._encode_items("buy")
        self.print_transaction(seller, "buy", items)

    def run_sell_session(self):
        """Selling mode: the trading post sells to a customer."""
        print("\n  🔵 SELLING MODE β€” You are selling to a customer.")
        customer = input("  Customer's name: ").strip().title()
        self.show_catalog()
        items = self._encode_items("sell")
        self.print_transaction(customer, "sell", items)

    def run(self):
        """Main entry point β€” choose buy or sell session."""
        self.show_banner()
        print("\n  Choose transaction mode:")
        print("  [1] BUY  β€” Accept copra/rubber from a seller")
        print("  [2] SELL β€” Sell copra/rubber to a customer")
        choice = input("  Enter 1 or 2: ").strip()
        if   choice == "1": self.run_buy_session()
        elif choice == "2": self.run_sell_session()
        else: print("  Invalid choice.")


# ── Entry point ────────────────────────────────────────────
if __name__ == "__main__":
    post = CopraRubberTradingPost()
    post.run()
00 β€”

Introduction β€” Why Copra & Rubber, Why Python?

Copra and rubber are not abstract commodities to millions of Filipino families β€” they are harvests, livelihoods, and generational businesses. Copra, the dried meat of the coconut, has been at the heart of Philippine agriculture since the Spanish colonial period and remains the country's largest coconut byproduct export. Natural rubber, cultivated primarily in Mindanao and parts of the Visayas, supports another layer of rural industry. Trading posts β€” locally called bagsakan or buying stations β€” sit at the center of this economy. They buy raw materials from farmers and small producers, then sell them onward to processors, exporters, and manufacturers at a higher price. This buy-sell spread is the engine of the trading post business, and it is precisely this dual-pricing reality that makes it such a rich vehicle for learning one of the most important ideas in all of software engineering: abstraction.

If you completed the rice.html tutorial, you already understand classes, dictionaries, loops, and f-strings. This tutorial goes further. Here, abstraction is not just a technique we use incidentally β€” it is the architectural foundation of the entire program. We use Python's built-in abc module to define a true abstract base class: a class that cannot be instantiated directly, that exists solely to specify a contract that all subclasses must fulfill. Every product in this system β€” whether copra or rubber β€” is required by the language itself to expose two properties: a buy_price and a sell_price. If a developer creates a new product class and forgets to implement either of those properties, Python raises a TypeError before the program even finishes loading. The contract is enforced not by trust, convention, or documentation β€” but by the runtime.

This is the difference between informal abstraction and formal abstraction. In the rice tutorial, abstraction meant hiding implementation details inside methods. Here, abstraction means defining an interface β€” a named set of capabilities that any conforming type must provide β€” and having the language verify compliance automatically. This is how serious, large-scale software is designed. Every major framework you will ever use β€” Django, FastAPI, SQLAlchemy, Pydantic β€” is built on exactly this principle. When you define a Django model, you are extending an abstract base. When Pydantic validates your schema, it enforces a property contract. The pattern you learn here is not a tutorial exercise; it is the foundation of professional Python architecture.

🧠
Abstraction as a spectrum: Abstraction exists on a continuum from informal to formal. At the informal end, you name a function and hide its implementation β€” the caller knows what it does, not how. At the formal end, you define an interface contract that the language itself enforces β€” subclasses must implement specific properties or methods, or instantiation is refused. This tutorial introduces you to the formal end of that spectrum, using Python's abc.ABC and @abstractmethod. Once you understand formal abstraction, you understand how every major framework, library, and design pattern is constructed. It is the vocabulary of professional software architecture.

The significance of abstraction runs deeper than keeping code organized. When you define TradableProduct as an abstract base, you are making a guarantee to every consumer of that interface: "Whatever object you receive from this system, you can always ask it for a buy_price and a sell_price." That guarantee is what allows modules to be developed, tested, and deployed independently of each other. The trading post's receipt engine does not need to know whether a product is copra or rubber β€” it just calls product.buy_price and gets a number. This decoupling is what makes software systems maintainable as they grow from hundreds of lines to hundreds of thousands. It is the reason that a team of fifty engineers can work on the same codebase without constantly breaking each other's code.

Abstraction also shapes how we reason about change. When business requirements evolve β€” say, the trading post decides to add coconut shell charcoal as a third commodity β€” the system does not need to be rewritten. A developer creates a new CharcoalProduct class that extends TradableProduct, implements the required properties, and the entire rest of the system β€” the receipt engine, the catalog display, the transaction logger β€” immediately supports the new product without a single change. This is called the Open/Closed Principle: the system is open for extension, closed for modification. Abstract base classes are the primary mechanism through which Python achieves this property.

πŸ₯₯

Real Dual Pricing

A trading post does not have one price β€” it has two. It pays farmers a buy price and charges processors a sell price. The margin between these prices is the business's profit. This dual-price model makes abstraction essential: each product must always expose both values, enforced by an interface contract that the language itself verifies at instantiation time.

🧱

True Abstraction (ABC)

Unlike rice.html which uses simple classes, this program uses Python's abc.ABC and @abstractmethod to create a compiler-enforced contract. Any product class that fails to implement buy_price or sell_price raises a TypeError at instantiation β€” the language itself guards the interface, not documentation or convention.

πŸ—οΈ

Three-Class Architecture

TradableProduct is the abstract base that defines the contract. CopraProduct and RubberProduct extend it with commodity-specific data and behavior. CopraRubberTradingPost orchestrates the business workflow. Adding a new commodity class requires zero changes to any existing code β€” true extensibility.

There is also a practical consequence of formal abstraction that beginner tutorials almost never mention: it makes your code dramatically easier to test. When every product is guaranteed to expose buy_price and sell_price, you can write a single test that verifies receipt totals correctly, and that test will work for every current and future product type without modification. When interfaces are enforced, tests can be written against the interface rather than against specific implementations β€” which is the foundation of mock-based testing, dependency injection, and every other advanced testing pattern you will encounter in professional codebases.

🐍
What makes this program different from rice.html: Here we introduce abc.ABC, @abstractmethod, @property, super().__init__(), isinstance() filtering, dual buy/sell pricing, and a transaction log with timestamps. You are no longer just writing classes β€” you are designing a contract-enforced inheritance hierarchy. This is the architecture of every real financial system, every ORM, every API schema library. The copra and rubber trading post is the business; formal abstraction is the engineering lesson.
πŸ‡΅πŸ‡­
For Filipino developers and agri-business students: The architectural patterns in this tutorial are directly applicable to digitizing real trading post operations across Mindanao, Quezon, and the Visayas. The TradableProduct abstract base mirrors a commodity instrument interface. CopraRubberTradingPost mirrors a service layer. The dual-pricing logic mirrors the exact financial structure used by UNICHEM, regional rubber cooperatives, and every NFA-accredited buying station. What you build here is not a simulation β€” it is a prototype of real business software.

01 β€”

Code Explanation β€” Step by Step

Step 1 β€” Abstract Base Class with abc.ABC
from abc import ABC, abstractmethod

class TradableProduct(ABC):
    @property
    @abstractmethod
    def buy_price(self): ...

    @property
    @abstractmethod
    def sell_price(self): ...

β‘  Why used

Inheriting from ABC makes TradableProduct an abstract class β€” you cannot instantiate it directly. Every concrete product must implement buy_price and sell_price or Python raises a TypeError before the program even runs.

β‘‘ Significance

This is a true interface contract. If you add a new commodity class (say, CoconutOilProduct) and forget to define sell_price, the system refuses to run. The abstract base acts as a compiler-enforced design rule across all future subclasses.

β‘’ How it operates

Combining @property with @abstractmethod requires subclasses to implement the method as a property β€” not just any method. This ensures product.buy_price (not product.buy_price()) always works uniformly across all commodity types.

Step 2 β€” CopraProduct & RubberProduct with super()
class CopraProduct(TradableProduct):
    def __init__(self, code, name, origin, grade, buy, sell):
        super().__init__(code, name, "kg", origin, grade)
        self._buy  = buy
        self._sell = sell

    @property
    def buy_price(self):  return self._buy

β‘  Why used

super().__init__() calls the parent class constructor to set shared attributes (code, name, unit, origin, grade). The child only handles its own unique data (_buy, _sell). This eliminates duplicated initialization code across subclasses.

β‘‘ Significance

The leading underscore in self._buy signals that this attribute is private by convention. External code should not modify it directly β€” instead it accesses product.buy_price via the property. This is a practical encapsulation pattern used throughout professional Python codebases.

β‘’ How it operates

When CopraProduct("C1", "Sun-Dried Copra", "Quezon", "Grade A", 28.00, 32.00) is called: first super().__init__ runs and sets the five shared fields, then the two child-specific lines set _buy=28.0 and _sell=32.0.

Step 3 β€” _encode_items() with mode parameter
def _encode_items(self, mode):
    items = []
    while True:
        code = input("Enter code (DONE to finish): ").strip().upper()
        if code == "DONE": break
        price = self.catalog[code].buy_price if mode=="buy" else self.catalog[code].sell_price
        items.append((code, qty, price, price*qty))

β‘  Why used

One method handles both buy and sell encoding by accepting a mode parameter. The only thing that changes between the two modes is which price to apply. This avoids duplicating the entire input loop β€” a core principle of DRY (Don't Repeat Yourself) programming.

β‘‘ Significance

The ternary buy_price if mode=="buy" else sell_price is a single-line conditional expression β€” a Pythonic way to choose between two values without a full if/else block. It reads naturally and executes efficiently.

β‘’ How it operates

Each item is stored as a 4-tuple: (code, quantity, price_used, subtotal). Tuples are used here because these values are computed once and should be treated as immutable. The list of tuples is then passed to print_transaction() for receipt generation.

Step 4 β€” isinstance() filtering in show_catalog()
for code, p in self.catalog.items():
    if isinstance(p, CopraProduct): p.display()
for code, p in self.catalog.items():
    if isinstance(p, RubberProduct): p.display()

β‘  Why used

isinstance() checks if an object belongs to a class or its subclass tree. By grouping copra and rubber separately in the catalog display, the output is organized and human-readable β€” sellers immediately see all copra prices together, then all rubber prices.

β‘‘ Significance

Because all products share the same dictionary, filtering by type is the clean way to separate them without maintaining separate catalogs. This is a polymorphism pattern: same container, heterogeneous types, unified interface (display()), type-specific output.

β‘’ How it operates

Python checks the object's __class__ and its entire MRO (Method Resolution Order). isinstance(p, TradableProduct) would be true for both copra and rubber objects β€” useful if you ever need to test that all items in the catalog are valid tradable products.

πŸ’‘
margin() method: The abstract base class defines margin() as a concrete method β€” return self.sell_price - self.buy_price. This is intentional: because both subclasses must implement the two abstract properties, the margin calculation is guaranteed to work for every product. Shared logic in base class, specific data in subclasses.

02 β€”

Key Python Concepts Reference

Concept / SyntaxWhat It IsHow It's Used Here
abc.ABCAbstract Base Class from Python's abc moduleParent of TradableProduct β€” cannot be instantiated
@abstractmethodEnforces method implementation in subclassesbuy_price & sell_price must exist in all products
@propertyConverts a method into an attribute accessorproduct.buy_price instead of product.buy_price()
super().__init__()Calls parent class constructorCopraProduct and RubberProduct share base fields
isinstance()Checks object class membershipFilters copra vs rubber for grouped catalog display
_private attrConvention for encapsulated attributes_buy, _sell β€” accessed only via properties
class X(Parent):Inheritance β€” child extends parent classCopraProduct and RubberProduct extend TradableProduct
ternary exprInline conditional: val_a if cond else val_bChoose buy_price or sell_price by mode parameter
tuple (a, b, c, d)Immutable ordered collectionTransaction items: (code, qty, price, subtotal)
sum(x for x in)Generator expression with sum()Grand total from all transaction items
datetime.now()Current timestamp from datetime moduleTimestamps receipts with date and time
list.append()Adds item to end of a listBuilds transaction log and item list
if __name__=="__main__"Module guard β€” direct execution onlySafe entry point; importable as a module

🎭 Polymorphism in Action

Both CopraProduct and RubberProduct implement a display() method, but each formats output differently β€” copra shows Grade, rubber shows Type. The trading post calls p.display() without caring which product it is. This is polymorphism: one interface, behavior determined by the actual object type.

🏦 Why Dual Pricing Matters

The buy-sell spread is the trading post's revenue engine. By making both prices required abstract properties, the program guarantees that no product can ever exist without both. A developer who forgets to set sell_price gets an immediate TypeError at startup β€” not a silent bug discovered at runtime.

πŸ‡΅πŸ‡­
Industry Context: Major copra processors like United Coconut Chemicals (UNICHEM) and rubber exporters like Cotabato trading houses operate on exactly this dual-price model. The TradableProduct abstract base mirrors a real-world financial instrument interface β€” any tradable commodity must expose both bid and ask prices.

03 β€”

Current Price Board

Reference table showing all buy and sell prices per kilogram. Green values are the prices paid to sellers; blue values are the prices charged to buyers. The spread between them is the trading post margin.

πŸ₯₯ Copra Products β€” per kilogram
BUY SELL
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 Products β€” per kilogram
BUY SELL
R1 β€” Cup Lump Standard (Grade 1)
β‚±52.00β‚±58.00
R2 β€” Cup Lump Premium (Grade 1)
β‚±55.00β‚±62.00
R3 β€” RSS-1 Smoked Sheet
β‚±68.00β‚±75.00
R4 β€” RSS-3 Smoked Sheet
β‚±60.00β‚±67.00
R5 β€” Technically Spec Rubber (TSR-20)
β‚±72.00β‚±80.00
πŸ“Š
Reading the Price Board: Green BUY = what the trading post pays the farmer or supplier. Blue SELL = what the trading post charges the processor or buyer. The difference is the gross margin. For example, Whole Copra Premium has a β‚±5.00/kg margin β€” a farmer delivering 1,000 kg earns β‚±33,000 while the trading post earns β‚±5,000 gross profit on that transaction.

04 β€”

Live Trade Demo β€” Buy & Sell

Interactive implementation of the Python program. Select a mode β€” buying from a seller or selling to a customer β€” then choose a product, enter quantity, and receive an official receipt with the correct price applied.

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

🟒 Official Buying Receipt β€” Valleys & Bytes Trading Post

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

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

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

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

Customerβ€”
Productβ€”
Grade / Typeβ€”
Sell Price / kgβ€”
Quantityβ€”
Amount to Collectβ€”

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


05 β€”

OOP & Abstraction Cheat Sheet

Abstract Class Syntax
from abc import ABC, abstractmethodimport tools
class X(ABC):define abstract class
@abstractmethodmust implement
@property + @abstractmethodabstract property
super().__init__(args)call parent init
Copra & Rubber Buy Prices
Sun-Dried Copraβ‚±28.00/kg
Kiln-Dried Copraβ‚±30.00/kg
Cup Lump Standardβ‚±52.00/kg
RSS-1 Sheetβ‚±68.00/kg
TSR-20 Rubberβ‚±72.00/kg
Copra & Rubber Sell Prices
Sun-Dried Copraβ‚±32.00/kg
Kiln-Dried Copraβ‚±35.00/kg
Cup Lump Standardβ‚±58.00/kg
RSS-1 Sheetβ‚±75.00/kg
TSR-20 Rubberβ‚±80.00/kg
Common Patterns
isinstance(obj, Cls)type check
a if cond else bternary
sum(s for _,_,_,s in)unpack tuple
datetime.now().strftime()format timestamp
self._attrprivate by convention

06 β€”

Glossary

abc.ABCAbstract Base Class. Inheriting from ABC makes a class uninstantiable. It exists only to be subclassed. Python raises TypeError if you try to instantiate it directly: TradableProduct() fails.
@abstractmethodA decorator that marks a method as required in any concrete subclass. If CopraProduct were to omit buy_price, Python would refuse to create any instance of it at all β€” enforced at class definition time.
@propertyConverts a method into a read-only attribute. product.buy_price looks like a simple data access but actually calls the buy_price(self) method behind the scenes. This hides implementation details from the caller.
super().__init__()Calls the parent class's __init__ method. Essential in inheritance so the parent can set its own attributes before the child sets its own. Without it, self.name, self.code, etc. would not be initialized in the parent.
isinstance(obj, Class)Returns True if obj is an instance of Class or any of its subclasses. Used here to separate copra from rubber in catalog display without maintaining two separate dictionaries.
PolymorphismThe ability for objects of different types to be treated through the same interface. Both CopraProduct and RubberProduct respond to .display() and .buy_price β€” but each produces type-specific behavior and output.
_private conventionA single leading underscore (e.g., self._buy) signals that the attribute is intended for internal use. Python does not strictly enforce this, but it's a widely respected convention that prevents accidental external modification.
CopraDried coconut meat extracted from the coconut shell. The primary raw material for coconut oil production. Graded by moisture content and processing method β€” sun-dried, kiln-dried, ball, or split. Prices fluctuate with global coconut oil markets.
RSS (Ribbed Smoked Sheet)A grade of natural rubber produced by coagulating latex, rolling it into sheets, and smoking to preserve them. RSS-1 is highest quality; RSS-3 has minor blemishes. Grades are standardized by the International Rubber Quality Packing Conference.
Cup LumpNatural rubber formed by spontaneous coagulation in latex collection cups. Lower quality than sheets but easier to produce. Common in smallholder rubber farming across Mindanao and Basilan. Prices are lower but volumes are high.
margin()A concrete method on the abstract base class: return self.sell_price - self.buy_price. Because both abstract properties are guaranteed to exist in every subclass, this computation is safe to define once in the parent. This is the correct place for shared logic.

07 β€”

Knowledge Quiz

Test your understanding of Python abstraction through the Copra & Rubber Trading System.

1. What happens if you try to instantiate TradableProduct() directly?
It creates an empty object with no attributes
Python raises a TypeError because it's an abstract class
It works the same as any regular class
βœ… Correct! TradableProduct inherits from ABC and has @abstractmethod decorators. Python raises TypeError: Can't instantiate abstract class TradableProduct with abstract methods buy_price, sell_price.
2. Why is super().__init__() called inside CopraProduct.__init__?
To make the program run faster
It is required by Python syntax for all classes
To initialize shared attributes (code, name, unit, origin, grade) defined in the parent class
βœ… Correct! Without super().__init__(), the five shared attributes defined in TradableProduct.__init__ would never be set. The child constructor only handles child-specific data (_buy, _sell); the parent handles the rest.
3. What is the purpose of combining @property and @abstractmethod?
It forces subclasses to implement the method as a property, allowing access without parentheses
It makes the method faster by caching its return value
It prevents subclasses from overriding the method
βœ… Correct! Combining both decorators requires the subclass to define buy_price as a property (not a regular method). This means callers write product.buy_price not product.buy_price() β€” a clean, uniform interface across all product types.
4. Why is isinstance(p, CopraProduct) used in show_catalog()?
To check if the product has a valid price
To filter and display copra products separately from rubber products from the same catalog dictionary
To raise an error if an invalid product is in the catalog
βœ… Correct! Both copra and rubber products share one dictionary. isinstance() lets us iterate it once for copra and once for rubber, printing each group under its own heading β€” a clean way to produce organized output from a heterogeneous container.
5. In _encode_items(mode), what does the ternary expression buy_price if mode=="buy" else sell_price accomplish?
It calculates the average between buy and sell price
It validates that the mode string is not empty
It selects the correct price to apply based on whether the transaction is a purchase from a seller or a sale to a customer
βœ… Correct! One method handles both buy and sell encoding by switching the price with a single ternary expression. This DRY approach avoids duplicating the entire input loop. The mode parameter acts as a strategy selector, a lightweight form of the Strategy design pattern.
Score: 0 / 5

08 β€”

Python OOP Learning Roadmap

1

Classes & Instances (Rice Tutorial)

Master __init__, self, instance vs class attributes. Build simple product classes from scratch. This is your OOP foundation β€” the rice.html tutorial covers this level comprehensively.

2

Abstract Classes & Interfaces (You Are Here)

Use abc.ABC and @abstractmethod to enforce method contracts in subclasses. Learn @property, super().__init__(), and isinstance(). Design extensible hierarchies where adding a new commodity type requires zero changes to existing code.

3

Encapsulation β€” Properties & Validation

Add @property.setter with price validation: reject negative values, enforce grade enums, validate origin strings. Protect your _buy and _sell attributes from being set to nonsensical values.

4

Magic Methods β€” Dunder Protocol

Make products comparable with __lt__ for sorting by margin. Add __repr__ for clean debugging output. Implement __add__ to merge two transaction objects β€” making your classes behave like built-in Python types.

5

Data Classes & Type Hints

Refactor products using @dataclass decorator and full type hints. Add Decimal for precise financial arithmetic (float rounding errors matter in real trading). Explore NamedTuple as an alternative to transaction tuples.

6

Frameworks β€” Django, FastAPI, SQLAlchemy

Your TradableProduct hierarchy maps directly to a Django abstract model. CopraProduct and RubberProduct become concrete Django models. The buy/sell logic becomes a Django view or FastAPI endpoint β€” the OOP skills transfer completely.

🎯
Next extension: Add a PremiumCopra(CopraProduct) subclass with a loyalty bonus method. Create a TradingSession class that holds multiple transactions and computes total profit for the day. Export the transaction log to CSV using Python's built-in csv module β€” real-world business reporting.

πŸ“€ Share This Resource

Spread Python OOP knowledge to Filipino developers and agri-business students everywhere

✨ Caption Generator

Generate a Custom Social Media Caption

Facebook LinkedIn X / Twitter WhatsApp YouTube Telegram
πŸ’¬ β€”

Comments & Discussion

Leave a Comment
4 COMMENTS
RM
Roberto Magnaye Trader
March 10, 2026
This is exactly what I needed. I run a small copra buying station in Quezon and wanted to digitize our records. Understanding the abstract class structure finally clicked for me here. The buy/sell dual pricing model mirrors exactly how we operate!
AL
Anna Lacsamana CS Student
March 8, 2026
The explanation of @abstractmethod combined with @property was something I searched for everywhere and never understood properly until now. The fact that Python enforces it at instantiation time β€” not at runtime β€” is a crucial distinction that most tutorials miss completely.
DC
Danilo Castillo Instructor
March 5, 2026
Using this for my OOP module at a tech school in Cotabato. Students from rubber farming communities immediately relate to the Cup Lump and RSS grading system. The dual-price margin concept also gives them a real economics lesson alongside the Python lesson. Highly recommended resource.
FV
Fatima Villanueva
March 1, 2026
Would love to see the next tutorial extend this with a PremiumCopra subclass using the Strategy pattern for variable margin rules. Also, adding a daily summary that exports to CSV would make this genuinely usable for small trading posts!

Stay Updated

Get New Python Tutorials Delivered

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

βœ“ You're subscribed! Welcome to Valleys & Bytes πŸ‡΅πŸ‡­
Weekly Python tutorials OOP project walkthroughs Free cheat sheets No spam, ever
πŸ‘€ β€”

Follow Me

Stay connected for more Python tutorials, Filipino agri-business coding projects, and developer content.

You Made It

You've Mastered Python OOP
Through Copra & Rubber

You have crossed a significant threshold. You did not just write code that runs β€” you designed a contract-enforced architecture that cannot be misused. You defined what it means to be a tradable product in code, and you let Python itself enforce that definition on every class that claims to be one. That discipline β€” the discipline of thinking in interfaces before thinking in implementations β€” is what distinguishes a software architect from a script writer.

πŸ”’
Next: Encapsulation

Add @property.setter validators that reject negative prices, enforce maximum margin rules, and validate product codes against a master list. True data protection built directly into your product classes β€” not enforced externally by hoping callers behave.

🧬
Then: Inheritance Depth

Create PremiumCopra(CopraProduct) with loyalty bonus pricing and FuturesRubber(RubberProduct) with forward contract logic. Explore multi-level inheritance trees and Python's method resolution order (MRO) in practice.

πŸš€
Goal: Production Systems

Every Django model, FastAPI schema, and SQLAlchemy entity is an abstract OOP class at its core. The TradableProduct hierarchy you built is architecturally identical to how financial instruments are modelled in production trading systems globally.

Pause and take inventory of what this single program taught you. You used abc.ABC to make TradableProduct uninstantiable β€” a class that exists only as a blueprint, never as an object. You stacked @property and @abstractmethod to require subclasses to implement buy_price and sell_price as properties specifically β€” not just as methods with any signature. You used super().__init__() to delegate shared attribute initialization to the parent, keeping subclasses lean and the parent authoritative over shared data. You used isinstance() to filter heterogeneous objects from a single dictionary, producing organized grouped output without maintaining separate catalogs. And you implemented a single _encode_items(mode) method with a ternary price selector that handles both buy and sell sessions without a single duplicated line. Each of these was a deliberate engineering decision, not a syntax exercise.

The deeper lesson is about trust. When a system is built on enforced interfaces, every module can trust that every object it receives will behave as expected. The receipt engine trusts that product.buy_price will always return a number, because the abstract contract guarantees it. The catalog display trusts that product.display() will always work, because the abstract contract requires it. This trust is what makes modular, testable, maintainable software possible. Without enforced interfaces, codebases become fragile systems held together by documentation and hope. With them, they become durable architectures that can grow, change, and be worked on by multiple developers without constant breakage.

🎯
Your challenge before moving on: Extend this system in three concrete ways. First, create a CoconutOilProduct(TradableProduct) class β€” verify that Python forces you to implement all abstract properties. Second, add a TradingSession class that accumulates multiple buy and sell transactions and reports total gross profit for the day. Third, add a CSV export method to TradingSession that writes the transaction log to a .csv file using Python's built-in csv module. Each of these extends the system without modifying any existing class β€” demonstrating the Open/Closed Principle in action. Build it, test it, break it on purpose by removing an abstract method, and observe the error. That moment of seeing Python refuse to instantiate a class is the moment the abstract contract becomes real to you.

Filipino technology companies β€” GCash, Maya, Ninja Van, Agrilyst, and dozens of agritech startups β€” are actively building systems that track commodity prices, automate trading workflows, and connect farmers to markets digitally. The architecture you practiced here is directly applicable to that work. The abstract base class pattern you learned is used in every Python ORM, every data validation library, every plugin system ever written. You started with a copra buying station. The skills you leave with are the vocabulary of professional software engineering. Ipagpatuloy mo. Kaya mo 'yan. Mabuhay tayong mga Filipino developer. πŸ‡΅πŸ‡­

← Back to Rice OOP Tutorial