A complete, line-by-line walkthrough of a Python OOP program — every method, every decision, every concept clarified from the ground up. Built with Philippine Pesos and real-world patterns you'll use in production.
Complete, syntax-highlighted Python — copy or download below
1 class FlowerShop: 2 def __init__(self): 3 self.flowers = { 4 "Rose": 50.00, 5 "Tulip": 40.00, 6 "Orchid": 60.00, 7 "Sunflower": 30.00, 8 "Lily": 55.00 9 } 10 11 def display_available_flowers(self): 12 """Display the list of available flowers with their prices.""" 13 print("Available Flowers in the Shop:") 14 for flower, price in self.flowers.items(): 15 print(f"{flower}: PHP {price:.2f} per stem") 16 17 def get_chosen_flower(self): 18 """Prompt the user to choose a flower from the available list.""" 19 while True: 20 chosen_flower = input("Enter the flower name: ").strip().title() 21 if chosen_flower in self.flowers: 22 return chosen_flower 23 else: 24 print("Invalid flower. Please choose from the list.") 25 26 def get_quantity(self): 27 """Prompt the user to enter the quantity of flowers.""" 28 while True: 29 try: 30 quantity = int(input("Enter the quantity: ")) 31 if quantity > 0: 32 return quantity 33 else: 34 print("Quantity must be a positive integer.") 35 except ValueError: 36 print("Invalid input. Please enter a positive integer.") 37 38 def calculate_total_cost(self, flower, quantity): 39 """Calculate and return total cost based on flower and quantity.""" 40 price_per_stem = self.flowers[flower] 41 total_cost = price_per_stem * quantity 42 return total_cost 43 44 def run(self): 45 """Main method to run the flower shop program.""" 46 self.display_available_flowers() 47 chosen_flower = self.get_chosen_flower() 48 quantity = self.get_quantity() 49 total_cost = self.calculate_total_cost(chosen_flower, quantity) 50 print(f"\nChosen Flower: {chosen_flower}") 51 print(f"Quantity: {quantity}") 52 print(f"Total Cost: PHP {total_cost:.2f}") 53 54 if __name__ == "__main__": 55 shop = FlowerShop() 56 shop.run()
This program is a gateway into real-world Python concepts. Here's why it matters for every beginner learning OOP.
self. Master this and every Python project becomes approachable.while True + try/except pattern used here is the gold standard for CLI input validation — used in production tools, not just beginner code.:.2f f-string format used for Philippine Pesos (PHP) is the exact technique used in e-commerce apps and financial dashboards.The FlowerShop program simulates a real-world flower purchasing system using Object-Oriented Programming — a class that bundles all related data and behaviour into one organized, reusable unit.
The user flow: browse available flowers → choose one → specify a quantity → receive a total cost in Philippine Pesos (PHP ₱).
What makes this program an excellent learning tool is that it is small enough to fully understand in one sitting, yet it demonstrates every major concept you will encounter in real Python applications: classes, constructors, instance variables, input validation, exception handling, dictionary operations, and f-string formatting — all working together in a coherent, purposeful flow.
Using a class instead of loose functions keeps all shop logic self-contained and easy to extend. Each method has exactly one responsibility — a principle called Single Responsibility. If you ever need to add a discount system, a loyalty program, or a receipt printer, you add a method — you do not rewrite the whole program.
As you read through the explanations, ask yourself: "Why was this designed this way?" Not just "what does this do?" Understanding intent is the difference between copying code and truly writing it.
class FlowerShop:
This declares a blueprint called FlowerShop. Without a class, all variables and functions would be scattered globally — hard to manage, hard to reuse, and impossible to instantiate multiple independent shops.
Think of the class as an architect's drawing. The drawing itself is not a building — it is a precise specification that can be used to construct as many identical buildings as needed. Every time you call FlowerShop(), Python uses this class definition as instructions to build a fresh, independent object in memory.
A class is a template. Writing FlowerShop() creates a live instance from it. The instance carries its own data and methods. Note there is no (object) after the class name — in Python 3, all classes implicitly inherit from object, so this is valid, modern Python.
Class names in Python use PascalCase (each word capitalised, no underscores). Functions and variables use snake_case. FlowerShop follows PEP 8 correctly. This consistency is why Python codebases are easy to read across teams and projects.
def __init__(self): self.flowers = { "Rose": 50.00, "Tulip": 40.00, "Orchid": 60.00, "Sunflower": 30.00, "Lily": 55.00 }
__init__ is the constructor — it fires automatically when a FlowerShop object is created. It builds self.flowers — the shop's inventory, mapping each flower name to its PHP price per stem. The double underscores make this a dunder method (double underscore). Python calls it automatically; you never invoke it directly.
self Keywordself refers to the specific instance being created. self.flowers attaches the dictionary to that object so every method in the class can access the same shared data without passing it as an argument each time. self is always the first parameter of every instance method, and Python passes it automatically.
| Data Type | Why It Was Chosen |
|---|---|
| dict (self.flowers) | Maps flower name → price directly; instant O(1) lookup by key |
| float (50.00) | Represents currency with decimal precision for PHP formatting |
| str keys ("Rose") | Human-readable; used for both display and validation simultaneously |
Defining self.flowers inside __init__ means every instance gets its own independent copy of the dictionary. If you defined it at class level, all instances would share the same dictionary — a subtle and dangerous bug when you have multiple shop objects modifying inventory.
def display_available_flowers(self): print("Available Flowers in the Shop:") for flower, price in self.flowers.items(): print(f"{flower}: PHP {price:.2f} per stem")
Before a customer can choose, they must see what is available. This method is the shop's menu board — always the first thing called. Isolating it means display logic can be updated without touching any other method.
| # | Code | What Happens |
|---|---|---|
| 1 | .items() | Returns each (name, price) pair as a tuple for simultaneous unpacking |
| 2 | for flower, price in … | Unpacks each tuple into two named variables cleanly |
| 3 | {price:.2f} | Format specifier — always shows exactly 2 decimal places (e.g. 50.00) |
| 4 | print(…) | Outputs the formatted string directly to the terminal |
:.2f matters for currencyWithout this format specifier, Python might print 50.0 instead of 50.00. In a financial context, showing two decimal places is not just stylistic — it communicates precision and professionalism. The same technique is used in every e-commerce checkout, banking app, and invoice system you have ever used.
def get_chosen_flower(self): while True: chosen_flower = input("Enter the flower name: ").strip().title() if chosen_flower in self.flowers: return chosen_flower else: print("Invalid flower. Please choose from the list.")
| Technique | What It Does & Why |
|---|---|
| .strip() | Removes accidental whitespace — " Rose " becomes "Rose" |
| .title() | Normalises case — "rOSE", "rose", "ROSE" all become "Rose" |
| while True: | Infinite loop — only exits via return when valid input is received |
| in self.flowers | Dictionary membership check — O(1) speed, clean and readable |
def get_quantity(self): while True: try: quantity = int(input("Enter the quantity: ")) if quantity > 0: return quantity else: print("Quantity must be a positive integer.") except ValueError: print("Invalid input. Please enter a positive integer.")
If the user types "abc" or "3.5", int() raises a ValueError. The except block catches it and prints a helpful message instead of crashing the program entirely.
Even if conversion succeeds, 0 or -5 must be rejected. These are logically invalid for a purchase even though they are mathematically valid integers.
The only escape is the return inside if quantity > 0. The loop keeps prompting until both layers of validation pass simultaneously. This guarantees the caller always receives a valid, usable value — never garbage.
def calculate_total_cost(self, flower, quantity): price_per_stem = self.flowers[flower] total_cost = price_per_stem * quantity return total_cost
Single Responsibility Principle — each method does exactly one thing. This makes the calculation easy to test, read, and modify later (for example, adding discounts or taxes) without touching display or input methods.
Imagine a future requirement: "Apply a 10% senior citizen discount." With this design, you only change one method. If the calculation lived inside run(), you would be mixing concerns — hunting through display code to find math code.
| # | Action | What Happens |
|---|---|---|
| 1 | Receive args | Gets flower (string) and quantity (integer) from the caller |
| 2 | Dict lookup | self.flowers[flower] retrieves the price in O(1) time |
| 3 | Multiply | price_per_stem × quantity — e.g. 50.00 × 3 = 150.00 |
| 4 | Return | Sends the number back to run() for display — caller decides what to do |
Returning gives the caller control. Printing inside the calculator mixes display with logic — a design flaw that reduces flexibility. With return, the same method can be used in a terminal app, a web API, a receipt generator, or a unit test — with zero changes. The caller decides how to use the result.
def run(self): self.display_available_flowers() chosen_flower = self.get_chosen_flower() quantity = self.get_quantity() total_cost = self.calculate_total_cost(chosen_flower, quantity) print(f"\nChosen Flower: {chosen_flower}") print(f"Quantity: {quantity}") print(f"Total Cost: PHP {total_cost:.2f}")
run() is the conductor. It calls every other method in the correct sequence. Without it, all individual methods would exist but never be coordinated into a complete user experience. Notice that run() does almost no work itself — it delegates everything.
| # | Call | What Happens |
|---|---|---|
| 1 | display_available_flowers() | Shows the menu so the user knows their options |
| 2 | get_chosen_flower() | Prompts and validates. Returns valid flower name |
| 3 | get_quantity() | Prompts and validates. Returns positive integer |
| 4 | calculate_total_cost(…) | Computes and returns the total cost as a float |
| 5 | print() × 3 | Displays the full order summary to the user |
run() reads like plain English: display, choose, get quantity, calculate, print. Each line maps directly to a specific, testable unit of behaviour. If anything breaks, you know exactly which method to investigate — the structure enforces clarity.
if __name__ == "__main__": shop = FlowerShop() shop.run()
The entry point — the starting gun. Python sets __name__ to "__main__" only when the file is executed directly, not when imported by another script. This guard prevents accidental execution.
Without this guard, importing flower_shop into a test suite would immediately launch the program and start prompting for input — a catastrophic side effect. The guard is a professional safety mechanism that every production Python script should include.
| Line | What It Does |
|---|---|
| if __name__ == "__main__": | Guard clause — only runs on direct execution, not on import |
| shop = FlowerShop() | Instantiates the object, triggering __init__ and building the dictionary |
| shop.run() | Launches the full user interaction sequence from start to finish |
When you run python flower_shop.py, Python sets __name__ = "__main__" for that file. When another script does import flower_shop, Python sets __name__ = "flower_shop" instead — and the guard block is skipped entirely. Two characters of context tell Python exactly what role the file is playing.
From the moment you run python flower_shop.py to the moment Python exits:
| Concept | Where & Why Used |
|---|---|
| OOP / Class | Entire program — bundles data and methods into one reusable unit |
| Encapsulation | self.flowers is private to the instance, shared across all methods |
| Constructor (__init__) | Guarantees inventory is set up before any method is called |
| Infinite Loop | get_chosen_flower & get_quantity — re-prompts until valid input received |
| Exception Handling | get_quantity — catches non-integer input, prevents crash |
| Dictionary Lookup | calculate_total_cost — O(1) price retrieval by key |
| f-Strings :.2f | display & run — currency always shows exactly 2 decimal places |
| .strip().title() | get_chosen_flower — case-insensitive, whitespace-tolerant input |
| Single Responsibility | Each method does exactly one thing — no mixed concerns anywhere |
| __name__ guard | Entry point — prevents accidental execution on import |
If you take only five things away from this tutorial, make them these:
FlowerShop holds its own flowers and all related methods. That is encapsulation — the foundation of every Python class you will ever write going forward.
Never write input() without a validation loop. The pattern in get_chosen_flower() is copy-paste ready for any project at any skill level.
ValueError from int() is caught, not crashed. Defensive programming means planning for what users will inevitably do wrong.
Each method does one thing. run() orchestrates, calculate_total_cost() calculates, display_…() displays. Change one without breaking the others.
if __name__ == '__main__'This guard lets you import your class without accidentally running the whole program. It is the mark of a professional, production-ready Python file.
Click each question to reveal the answer. Use these to check your understanding before moving on to the next tutorial.
while True loop from get_chosen_flower()?›else branch would print an error — but then the function would end and return None. The program would crash when run() tried to use None as a dictionary key. The while True loop is essential for keeping the prompt alive until valid input is received.get_quantity() need both a try/except and an if quantity > 0 check?›int("abc") raises a ValueError before any comparison can happen. But even after a successful int() conversion, the user could type 0 or -3 — valid integers but logically impossible quantities. The if quantity > 0 check catches this second category. You need both layers because they defend against completely different classes of invalid input.FlowerShop instances and added a flower to one, would the other be affected?›self.flowers is defined inside __init__, each instance gets its own independent copy of the dictionary. Modifying shop1.flowers["Carnation"] = 35.00 has zero effect on shop2.flowers. This is why instance variables (defined with self inside __init__) are safer than class-level variables — each object owns its own data..strip().title() do, and why is the order important?›.strip() removes leading and trailing whitespace first — turning " rose " into "rose". Then .title() capitalises the first letter of each word — turning "rose" into "Rose". The order matters: if you called .title() first on " rose", the leading space could interfere with capitalisation behaviour depending on the Python version. Strip always comes first — clean before you transform.def apply_discount(self, total, rate=0.10): return total * (1 - rate). Then in run(), ask is_senior = input("Senior citizen? y/n: ").lower() == 'y', and apply: if is_senior: total_cost = self.apply_discount(total_cost). Because calculate_total_cost() returns a value instead of printing directly, you can manipulate the result freely before displaying it. This is exactly why separation of concerns matters.:.2f mean inside an f-string, and when should you use it?›: introduces the format specification. .2 means "2 digits after the decimal point". f means "format as a fixed-point float." Together, :.2f guarantees the number always displays with exactly two decimal places — 50.0 becomes 50.00, and 150.555 becomes 150.56 (rounded). Use it any time you display currency, measurements, or percentages where precision matters to the user.These are the most frequent errors students make when first writing programs like FlowerShop — and how to fix them correctly.
Printing inside calculate_total_cost() mixes display logic with business logic. The method becomes impossible to reuse silently in tests, APIs, or receipt systems. Always return computed values.
Without a try/except block, typing "abc" when a number is expected will crash the program with an unhandled ValueError. Always wrap int(input()) in a try block.
Placing flowers = {} at class level means all instances share the same dictionary. Changing it in one object changes it everywhere — a silent, dangerous bug.
Without if __name__ == "__main__":, importing your file in a test or another script immediately executes the program and prompts for input — completely breaking your test suite.
The essential patterns used in FlowerShop — ready to copy into your own projects.
Understanding why the code is designed this way unlocks every Python project you will ever build.
shop_manila = FlowerShop()Once you understand FlowerShop, here are natural next steps to level up your Python skills progressively.
sqlite3 module.unittest to test calculate_total_cost() and input validation logic. Testing OOP code is a critical skill for any professional Python developer.Join 2,400+ developers in the Philippines and beyond. New tutorials, code breakdowns, and project ideas — every week, free.
Discussion
Share your questions, insights, or code experiments. All skill levels are welcome here.
✍️ Leave a Comment
3 Comments