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.
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.
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.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.Code Explanation β Step by Step
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.
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.
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.
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() 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.Key Python Concepts Reference
| Concept / Syntax | What It Is | How It's Used Here |
|---|---|---|
| abc.ABC | Abstract Base Class from Python's abc module | Parent of TradableProduct β cannot be instantiated |
| @abstractmethod | Enforces method implementation in subclasses | buy_price & sell_price must exist in all products |
| @property | Converts a method into an attribute accessor | product.buy_price instead of product.buy_price() |
| super().__init__() | Calls parent class constructor | CopraProduct and RubberProduct share base fields |
| isinstance() | Checks object class membership | Filters copra vs rubber for grouped catalog display |
| _private attr | Convention for encapsulated attributes | _buy, _sell β accessed only via properties |
| class X(Parent): | Inheritance β child extends parent class | CopraProduct and RubberProduct extend TradableProduct |
| ternary expr | Inline conditional: val_a if cond else val_b | Choose buy_price or sell_price by mode parameter |
| tuple (a, b, c, d) | Immutable ordered collection | Transaction 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 module | Timestamps receipts with date and time |
| list.append() | Adds item to end of a list | Builds transaction log and item list |
| if __name__=="__main__" | Module guard β direct execution only | Safe 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.
TradableProduct abstract base mirrors a real-world financial instrument interface β any tradable commodity must expose both bid and ask prices.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.
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.
π’ Official Buying Receipt β Valleys & Bytes Trading Post
Salamat! Transaction recorded β Valleys & Bytes Trading Post π΅π
π΅ Official Selling Receipt β Valleys & Bytes Trading Post
Maraming Salamat! β Valleys & Bytes Trading Post π΅π
OOP & Abstraction Cheat Sheet
Glossary
ABC makes a class uninstantiable. It exists only to be subclassed. Python raises TypeError if you try to instantiate it directly: TradableProduct() fails.CopraProduct were to omit buy_price, Python would refuse to create any instance of it at all β enforced at class definition time.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.__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.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.CopraProduct and RubberProduct respond to .display() and .buy_price β but each produces type-specific behavior and output.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.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.Knowledge Quiz
Test your understanding of Python abstraction through the Copra & Rubber Trading System.
TradableProduct() directly?TypeError because it's an abstract classTradableProduct inherits from ABC and has @abstractmethod decorators. Python raises TypeError: Can't instantiate abstract class TradableProduct with abstract methods buy_price, sell_price.super().__init__() called inside CopraProduct.__init__?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.@property and @abstractmethod?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.isinstance(p, CopraProduct) used in show_catalog()?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._encode_items(mode), what does the ternary expression buy_price if mode=="buy" else sell_price accomplish?Python OOP Learning Roadmap
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.
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.
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.
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.
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.
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.
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.