Share
VALLEYS & BYTES
Return to Encapsulation Hub

Python OOP ยท Encapsulation ยท Lechon Sales

Source Code Explained โ€”
Lechon Sales Edition

A complete walkthrough of a Python OOP Lechon Sales management system โ€” every class, every method, every price. Extended with OOP theory, a live order form, a cheat sheet, and a glossary.

Python 3.x OOP ยท Classes ยท Encapsulation Inheritance ยท Properties Beginner โ†’ Intermediate ๐Ÿ‡ต๐Ÿ‡ญ Philippines
๐Ÿ“ฆ
System Overview
3 Classes ยท 3 Products ยท OOP: Encapsulation + Inheritance + Composition
โ‚ฑ750
Pork / kilo
โ‚ฑ280
Chicken / whole
โ‚ฑ350
Tuna / kilo
โ‚ฑ7,390
Demo Revenue
lechon_sales.py โ€” Full Source
class Product:
    """Base class for all products sold in the lechon business."""

    def __init__(self, name: str, price: float, quantity: int = 0):
        self._name     = name        # protected attribute
        self._price    = price       # protected attribute
        self._quantity = quantity    # protected attribute

    @property
    def name(self) -> str:
        return self._name

    @property
    def price(self) -> float:
        return self._price

    @price.setter
    def price(self, new_price: float):
        if new_price <= 0:
            raise ValueError("Price must be positive")
        self._price = new_price

    @property
    def quantity(self) -> int:
        return self._quantity

    @quantity.setter
    def quantity(self, new_quantity: int):
        if new_quantity < 0:
            raise ValueError("Quantity cannot be negative")
        self._quantity = new_quantity

    def __str__(self) -> str:
        return f"{self._name} - โ‚ฑ{self._price:.2f} (Stock: {self._quantity})"


class PorkLechon(Product):
    def __init__(self, quantity: int = 0):
        super().__init__("Pork Lechon", 750.00, quantity)


class ChickenLechon(Product):
    def __init__(self, quantity: int = 0):
        super().__init__("Whole Roasted Chicken", 280.00, quantity)


class TunaPanga(Product):
    def __init__(self, quantity: int = 0):
        super().__init__("Tuna Panga", 350.00, quantity)


class LechonBusiness:

    def __init__(self, business_name: str):
        self._business_name = business_name
        self._products      = []        # list of Product objects
        self._total_revenue = 0.0     # private attribute

    def add_product_stock(self, product: Product) -> None:
        for p in self._products:
            if p.name == product.name:
                p.quantity += product.quantity
                return
        self._products.append(product)

    def sell_product(self, product_name: str, quantity: float) -> None:
        for p in self._products:
            if p.name.lower() == product_name.lower():
                if p.quantity >= quantity:
                    p.quantity -= quantity
                    revenue = quantity * p.price
                    self._total_revenue += revenue
                    print(f"Sold {quantity} {p.name}. Revenue: โ‚ฑ{revenue:.2f}")
                    return
                else:
                    print(f"Not enough stock for {p.name}.")
                    return
        print(f"Product '{product_name}' not found.")

    def show_inventory(self) -> None:
        print(f"\n--- {self._business_name} Inventory ---")
        for p in self._products:
            print(p)
        print("----------------------------------\n")

    @property
    def total_revenue(self) -> float:
        return self._total_revenue

    @property
    def business_name(self) -> str:
        return self._business_name


if __name__ == "__main__":
    business = LechonBusiness("Mang Juan's Lechon House")

    business.add_product_stock(PorkLechon(50))
    business.add_product_stock(ChickenLechon(20))
    business.add_product_stock(TunaPanga(30))

    business.show_inventory()

    business.sell_product("Pork Lechon",           5)
    business.sell_product("Whole Roasted Chicken",  3)
    business.sell_product("Tuna Panga",             8)

    print(f"\nTotal Revenue: โ‚ฑ{business.total_revenue:,.2f}")
00 โ€”

What is Object-Oriented Programming?

Before dissecting the code, it helps to understand the thinking behind OOP. Every line in this program was shaped by the same principles that define how professional Python code is structured.

๐Ÿ”’

Encapsulation

Each class bundles its data and the methods that operate on it. Product guards its price and quantity through validated property setters โ€” external code cannot corrupt them directly.

๐Ÿงฌ

Inheritance

PorkLechon, ChickenLechon, and TunaPanga all inherit from Product. They reuse the parent's logic and simply provide their own name and price โ€” no duplicated code.

๐Ÿ”—

Separation of Concerns

Product knows about a single item. LechonBusiness knows about the inventory and revenue. Each layer does exactly one job and does it well.

๐Ÿ
Why Python properties? The @property decorator lets you expose attributes as if they were plain variables while secretly running validation logic. External code writes product.price = 500 and the setter silently checks it is positive โ€” clean public API, safe internals.
1

Define the blueprint โ€” class Product

A class is a template. Product describes what every item in the business holds: a name, price, and quantity. Each specific product (pork, chicken, tuna) is an instance stamped from that template.

2

Extend with subclasses โ€” PorkLechon(Product)

Writing class PorkLechon(Product) means PorkLechon is a Product. It calls super().__init__() to run the parent's setup with the correct name and price โ€” inheriting all getters, setters, and __str__ for free.

3

Compose into a business โ€” LechonBusiness

LechonBusiness holds a list of Product objects. One class containing instances of another is called composition โ€” the standard way to build complex systems from small, tested pieces.

4

Guard data with validated setters

Setting product.price = -100 calls the @price.setter, which raises ValueError. This is encapsulation in action โ€” the class protects its own data integrity, even against programmer mistakes.


01 โ€”

The Product Base Class

Product.__init__ โ€” constructor lechon_sales.py
class Product:
    def __init__(self, name, price, quantity=0):
        self._name     = name      # protected โ€” leading underscore by convention
        self._price    = price
        self._quantity = quantity

โ‘  Why used

The single underscore prefix on _name, _price, _quantity signals to other developers: "don't touch these directly." Access is controlled through properties, giving the class full authority over its own data.

โ‘ก Significance

Making Product a base class means every product in the system โ€” pork, chicken, tuna โ€” shares the same interface. Code that works with a Product works with any of its subclasses automatically.

โ‘ข How it operates

When you call PorkLechon(10), Python runs Product.__init__ via super(), storing "Pork Lechon", 750.00, and 10 as protected attributes on the new instance.

โš ๏ธ
Protected vs Private in Python: One underscore _attr = protected by convention (accessible but don't). Two underscores __attr = name-mangled by Python to prevent accidental override. This code uses one underscore, meaning the convention is enforced by discipline, not by the language.

02 โ€”

The Product Subclasses

Three concrete product classes
class PorkLechon(Product):
    def __init__(self, quantity=0):
        super().__init__("Pork Lechon", 750.00, quantity)

class ChickenLechon(Product):
    def __init__(self, quantity=0):
        super().__init__("Whole Roasted Chicken", 280.00, quantity)

class TunaPanga(Product):
    def __init__(self, quantity=0):
        super().__init__("Tuna Panga", 350.00, quantity)
CodeProductUnitPrice
1Pork Lechonper kiloโ‚ฑ750.00
2Whole Roasted Chickenper wholeโ‚ฑ280.00
3Tuna Pangaper kiloโ‚ฑ350.00

โ‘  Why used

Each subclass represents a real product with a fixed, known price. Encoding price at the class level means there's one authoritative source โ€” change PorkLechon once and it's updated everywhere.

โ‘ก Significance

super().__init__(...) delegates initialization to the parent. The subclass only passes its specific values; all the property validation, __str__ formatting, and getter/setter logic is inherited automatically.

โ‘ข How it operates

Calling TunaPanga(30) triggers Product.__init__("Tuna Panga", 350.00, 30). The resulting object has .name, .price, .quantity, and __str__ all working immediately โ€” zero extra code.


03 โ€”

The LechonBusiness Class

LechonBusiness.__init__ and add_product_stock()
class LechonBusiness:
    def __init__(self, business_name):
        self._business_name = business_name
        self._products      = []       # composition: list of Product objects
        self._total_revenue = 0.0

    def add_product_stock(self, product):
        for p in self._products:
            if p.name == product.name:
                p.quantity += product.quantity  # merge stock
                return
        self._products.append(product)        # new product

โ‘  Why used

LechonBusiness is the container that manages the full inventory. Separating business logic from product logic keeps each class focused on one responsibility โ€” a core principle of good OOP design.

โ‘ก Significance

_total_revenue is read-only via @property total_revenue โ€” external code can read it but never accidentally overwrite it. This is encapsulation protecting business-critical data.

โ‘ข How it operates

add_product_stock() searches existing products by name. If found, it adds to stock (prevents duplicates). If not, it appends the new product. This makes re-stocking and first-time adding work with the same method call.


04 โ€”

The sell_product() Method

Guard clauses ยท Stock deduction ยท Revenue tracking
def sell_product(self, product_name, quantity):
    for p in self._products:
        if p.name.lower() == product_name.lower():
            if p.quantity >= quantity:            # Guard โ€” enough stock?
                p.quantity -= quantity
                revenue = quantity * p.price
                self._total_revenue += revenue
                print(f"Sold {quantity} {p.name}. Revenue: โ‚ฑ{revenue:.2f}")
                return
            else:
                print(f"Not enough stock for {p.name}.")  # Guard fail
                return
    print(f"Product '{product_name}' not found.")

โ‘  Why used

Validation ensures no revenue is counted and no stock deducted unless the product exists and has sufficient quantity. Both guards exit early with a clear message, keeping the success path flat and readable.

โ‘ก Significance

Case-insensitive matching (.lower()) means "pork lechon", "PORK LECHON", and "Pork Lechon" all find the same product โ€” matching how a real cashier would enter a name.

โ‘ข How it operates

On success: p.quantity -= quantity calls the quantity setter (which validates โ‰ฅ 0), then revenue = quantity * p.price computes the total and accumulates it into the private _total_revenue.

๐Ÿ’ก
Revenue formula: revenue = quantity * p.price โ€” for kilo-based products this is kilos ร— price per kilo. For chicken it is count ร— โ‚ฑ280. The same formula works for both because the unit is embedded in the price definition of each subclass.

05 โ€”

The Main Execution Block

if __name__ == "__main__"
if __name__ == "__main__":
    business = LechonBusiness("Mang Juan's Lechon House")

    business.add_product_stock(PorkLechon(50))
    business.add_product_stock(ChickenLechon(20))
    business.add_product_stock(TunaPanga(30))

    business.show_inventory()

    business.sell_product("Pork Lechon",          5)   # 5 kilos
    business.sell_product("Whole Roasted Chicken", 3)   # 3 whole
    business.sell_product("Tuna Panga",            8)   # 8 kilos

    print(f"\nTotal Revenue: โ‚ฑ{business.total_revenue:,.2f}")

โ‘  Why used

The if __name__ == "__main__" guard ensures this block only runs when the script is executed directly โ€” not when it is imported as a module. This is the standard Python entry-point pattern.

โ‘ก Significance

The expected total revenue from this run is (5ร—750) + (3ร—280) + (8ร—350) = โ‚ฑ7,390.00. The read-only total_revenue property means the business always reports accurate totals โ€” no external code can tamper with it.

โ‘ข How it operates

Stock is set up first via add_product_stock(), then show_inventory() prints all products, then each sell_product() call validates, deducts stock, and accumulates revenue in sequence.


06 โ€”

Live Order Form

The interactive implementation of the program above. Select a product, enter the quantity, and confirm to compute the total cost.

Encode Your Order โ€” Mang Juan's Lechon House
Total Cost
โ€”
โ‚ฑ0.00

โœ… Order Receipt

Productโ€”
Unit Priceโ€”
Quantityโ€”
Total Cost โ€”

Thank you for your purchase โ€” Mang Juan's Lechon House ๐Ÿ‡ต๐Ÿ‡ญ


07 โ€”

OOP Cheat Sheet

Class Syntax
class Foo:define a class
class Bar(Foo):inherit from Foo
def __init__(self)constructor
super().__init__()call parent constructor
def __str__(self)string representation
Encapsulation
self._attrprotected (convention)
self.__attrprivate (name-mangled)
@propertygetter decorator
@x.settersetter with validation
raise ValueErrorreject invalid data
Prices
PorkLechonโ‚ฑ750.00 / kilo
ChickenLechonโ‚ฑ280.00 / whole
TunaPangaโ‚ฑ350.00 / kilo
revenueqty ร— price
Common Patterns
if __name__ == "__main__"entry point guard
for p in list: if ...: returnearly return / guard clause
p.name.lower()case-insensitive match
list.append(obj)add to inventory

08 โ€”

Glossary

classA blueprint that defines the attributes and methods of an object. Product is the blueprint; PorkLechon(10) is an instance built from it.
__init__The constructor method called automatically when a new object is created. It sets up the initial state of the object by assigning values to attributes.
selfA reference to the current instance. Every instance method receives self as its first argument so it can read and modify its own attributes.
inheritanceA mechanism where a subclass automatically receives all attributes and methods of its parent class. PorkLechon inherits __str__, getters, and setters from Product.
encapsulationBundling data and the methods that operate on it inside a class, and restricting direct access to the data through protected attributes and validated property setters.
@propertyA decorator that turns a method into a readable attribute. Paired with @x.setter it lets you add validation logic that runs transparently on every write.
super()Returns a proxy to the parent class. super().__init__(...) runs the parent's constructor, letting the subclass reuse initialization logic without duplicating it.
guard clauseAn early return or raise that exits a function immediately if a precondition fails, keeping the main success logic flat and readable rather than deeply nested.
compositionOne class holding instances of another as attributes. LechonBusiness holds a list of Product objects โ€” it is composed of products, not a subclass of them.

09 โ€”

Why OOP Matters in the Real World

Understanding OOP is not just about syntax โ€” it is about how professional developers think when building software that scales, evolves, and survives production environments. The lechon sales system is a microcosm of every serious Python application.

๐Ÿ”ง Maintainability

When prices change at Mang Juan's, you update exactly one line โ€” the constructor argument in PorkLechon. OOP code has a single source of truth for each piece of data, which drastically reduces bugs introduced by inconsistent manual updates across multiple files.

๐Ÿงช Testability

Because Product encapsulates its state and validates input, writing unit tests is straightforward. You can test price setter rejection independently without spinning up an entire sales system โ€” each class is a self-contained unit with predictable behavior.

๐Ÿ“ฆ Reusability

Adding a new product โ€” say, BeefCaldereta โ€” requires only a three-line subclass. All the business logic in LechonBusiness works immediately because the new class inherits the same interface. This is the power of the Liskov Substitution Principle in action.

๐Ÿš€ Scalability

This same OOP architecture powers enterprise systems. Django models, SQLAlchemy entities, FastAPI Pydantic schemas โ€” all use the same class-based design. Mastering OOP at the lechon level means your mental model transfers directly to production-grade Python frameworks.

๐ŸŽฏ
Industry insight: Filipino tech companies like Grab, PayMaya, and Paymongo build Python microservices using exactly these patterns. A Product base class mirrors an abstract BaseModel; a LechonBusiness mirrors a service layer. You are learning the right vocabulary.
๐Ÿ’ก

Convention vs. Enforcement: The Single Underscore Rule

Python uses _attr (single underscore) as a convention for "protected" attributes โ€” other programmers should not access them directly, but Python does not actually prevent it. Double underscore __attr triggers name-mangling, making access from outside the class genuinely difficult. This system exemplifies Python's philosophy: "We're all consenting adults here."

๐Ÿ’ก

Type Hints Are Not Enforced โ€” But They Are Essential

The name: str, price: float, and -> None annotations in this code are not enforced by Python at runtime. They exist for readability, IDE autocomplete, and static analysis tools like mypy. In professional codebases, type hints are treated as first-class documentation โ€” and tools like pyright will flag violations in CI pipelines before code ever reaches production.

๐Ÿ’ก

Why float for Quantity in sell_product()?

Pork lechon and tuna panga are sold by the kilo, so the quantity can be fractional โ€” e.g., 2.5 kilos. Using float for quantity in sell_product() rather than int handles this gracefully. In a production system you might use Python's decimal.Decimal to avoid floating-point precision issues in financial calculations.


10 โ€”

OOP Use Cases Beyond Lechon

Once you understand the Productโ€“LechonBusiness pattern, you can apply it to virtually any domain. The same encapsulation and inheritance structure solves radically different problems.

๐Ÿฆ

Banking System

A BankAccount base class with SavingsAccount and CheckingAccount subclasses. Property setters prevent negative balances; a Bank composition class manages all accounts and total assets.

๐Ÿ›’

E-commerce Store

A Product base class with PhysicalProduct, DigitalProduct subclasses overriding a deliver() method differently. A ShoppingCart composes products exactly like LechonBusiness does.

๐ŸŽฎ

Game Development

A Character base class with Warrior, Mage, Rogue subclasses inheriting health and attack(). Properties guard HP against invalid values. A GameWorld composition manages all entities.

๐Ÿฅ

Hospital System

A Patient base class extended by InPatient and OutPatient subclasses. Validated setters prevent negative medication doses. A Hospital class holds all patients and computes total billing.

๐Ÿ“š

Library Management

A LibraryItem base with Book, Magazine, DVD subclasses. Properties control checkout status. A Library composition class manages inventory and overdue tracking.

๐Ÿš—

Vehicle Fleet

A Vehicle base with Car, Truck, Motorcycle subclasses each overriding fuel_cost(). A Fleet composition class tracks total mileage, maintenance, and revenue per vehicle.


11 โ€”

Python OOP Learning Roadmap

This tutorial covers the core of OOP. Here is where to go next to build genuine professional Python expertise.

Step 01 โ€” Foundations

Classes & Instances

Master __init__, self, and the difference between class attributes and instance attributes. Build 5โ€“10 simple classes from scratch without inheritance.

Step 02 โ€” Encapsulation

Properties & Validation

Learn @property, @x.setter, and @x.deleter. Practice writing setters that raise ValueError or TypeError for invalid input.

Step 03 โ€” Inheritance

Subclasses & super()

Build class hierarchies with 2โ€“3 levels. Understand method resolution order (MRO) and when to call super() vs. override completely.

Step 04 โ€” Magic Methods

Dunder Methods

Go beyond __init__ and __str__. Learn __repr__, __eq__, __lt__, __add__, and __len__ to make classes behave like built-in types.

Step 05 โ€” Advanced OOP

Abstract Classes & Interfaces

Use abc.ABC and @abstractmethod to enforce method implementation in subclasses. Learn the difference between abstract classes, interfaces, and mixins.

Step 06 โ€” Frameworks

OOP in Django & FastAPI

Apply your OOP knowledge to real frameworks. Django models are classes with inherited save/query methods. FastAPI Pydantic models use @validator โ€” your property pattern, production-hardened.

๐Ÿ‡ต๐Ÿ‡ญ
Filipino Developer Path: This OOP foundation prepares you for roles in fintech, logistics, and e-commerce tech โ€” the fastest-growing Python sectors in the Philippines. Companies like Mynt (GCash), Ninja Van, and Lalamove all run Python services on exactly these patterns.

Stay Updated

Get New Python Tutorials Delivered

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

โœ“ You're subscribed! Welcome to the Mang Juan's Python community ๐Ÿ‡ต๐Ÿ‡ญ
Weekly Python tutorials OOP project walkthroughs Free cheat sheets No spam, ever
๐Ÿ’ฌ โ€”

Comments & Discussion

5 COMMENTS
Leave a Comment
MR
Maria Reyes Student
March 5, 2026
This tutorial finally made @property click for me. I kept wondering why not just use getters like in Java, but the clean public API argument makes total sense. The lechon example is so relatable โ€” much better than generic "Animal" class examples.
โ™ฅ 12 likes
JD
Jose Dela Cruz Developer
February 28, 2026
The composition vs. inheritance distinction in section 05 is crucial and often glossed over in beginner tutorials. LechonBusiness has products, it is not a product โ€” that sentence alone saved me from a design mistake in my current project.
โ™ฅ 9 likes
AC
Ana Cruz
February 20, 2026
Question: why use float for quantity in sell_product instead of int? I tried to change it to int and got errors on the 2.5 kilo orders. Now I understand โ€” lechon is sold by weight, not count. Real-world constraints driving design decisions. Love it.
โ™ฅ 7 likes
BT
Benjie Torres Instructor
February 14, 2026
I am using this walkthrough in my Python class at a Manila coding bootcamp. Students connect with the lechon context immediately โ€” it grounds abstract OOP concepts in a business they understand. The guard clause explanation in the sell_product section is particularly well-written. Bookmarked and shared.
โ™ฅ 21 likes
LN
Liza Navarro
January 30, 2026
Would love to see a follow-up tutorial extending this system with a Discount class using composition, and perhaps a report_summary() method that formats revenue per product. Also curious how you would handle the case where the same product exists in two branches of the business.
โ™ฅ 5 likes