Modern Python Approach
Modern Object‑Oriented Programming Made Simple
1. Introduction to OOP & Type Hints
Theory:
- Classes are templates for creating objects (e.g.,
UgaliRecipeblueprint). - Objects are class instances with unique data (e.g.,
ugali_for_4 = Ugali(servings=4)). - Type Hints explicitly declare data types for:
- Parameters (
def __init__(self, name: str)) - Return values (
-> None) - Variables (
price: float = 149.99)
2. Classes & Objects with Type Hints
class Chef:
def __init__(self, name: str, specialty: str) -> None:
self.name: str = name
self.specialty: str = specialty
def introduce(self) -> None:
print(f"I'm Chef {self.name}, expert in {self.specialty}!")
# Create instance
jambo: Chef = Chef("Shaba-2", "African Cuisine")
jambo.introduce()Terminal Output:
I'm Chef Shaba-2 , expert in African Cuisine!Type Hints Breakdown:
name: str→ Parameter must be string-> None→ Method returns nothingShaba-2: Chef→ Variable holds Chef instance
3. Attributes & Methods
Instance Method:
class UgaliPot:
def __init__(self, material: str) -> None:
self.material: str = material
def describe(self) -> str:
return f"Traditional {self.material} pot for perfect Ugali"
pot = UgaliPot("clay")
print(pot.describe())Terminal Output:
Traditional clay pot for perfect UgaliType Hint: Method returns string (-> str)
Class Method:
class Restaurant:
chain_name: str = "Nyiragongo Flavors"
@classmethod
def change_chain(cls, new_name: str) -> None:
cls.chain_name = new_name
Restaurant.change_chain("Virunga's food")
print(Restaurant.chain_name)Terminal Output:
Virunga's foodStatic Method:
class CookingUtils:
@staticmethod
def is_hot(temp: float) -> bool:
return temp > 60
print(CookingUtils.is_hot(75.5))Terminal Output:
True4. Encapsulation & Abstraction
class SecretRecipe:
def __init__(self) -> None:
self.__secret_spice = "Baobab Powder"
@property
def spice(self) -> str:
return "Secret ingredient protected!"
def reveal(self, password: str) -> None:
if password == "JikoniMagic":
print(f"Secret spice: {self.__secret_spice}")
recipe = SecretRecipe()
print(recipe.spice) # Access via property
recipe.reveal("wrongpass") # Won't revealTerminal Output:
Secret ingredient protected!Type Hints:
-> strfor property getterpassword: strenforces string input
OOP Concepts:
- Encapsulation:
__secret_spiceis hidden - Abstraction: Exposes simple
spiceproperty
5. Inheritance & Polymorphism
class Dish:
def serve(self) -> str:
raise NotImplementedError
class Ugali(Dish):
def serve(self) -> str:
return "Serving with Nderema!"
class Chapati(Dish):
def serve(self) -> str:
return "Serving rolled with Maharagi!"
menu = [Ugali(), Chapati()]
for item in menu:
print(item.serve())Terminal Output:
Serving with Nderema!
Serving rolled with Maharagi!Type Hints:
- Consistent
-> stracross hierarchy
OOP Concepts:
- Inheritance: Child classes extend
Dish - Polymorphism: Different
serve()implementations
6. Modern Python Features
Dataclass:
from dataclasses import dataclass
@dataclass
class Customer:
name: str
loyalty_points: int = 0
mama_ngina = Customer("Nyaba-2", 150)
print(mama_ngina)Terminal Output:
Customer(name='Nyaba-2', loyalty_points=150)Type Hints with Collections:
from typing import Dict, Tuple
class MealPrep:
def __init__(self):
self.orders: Dict[int, Tuple[str, str]] = {}
def add_order(self, table: int, main: str, soup: str) -> None:
self.orders[table] = (main, soup)
prep_station = MealPrep()
prep_station.add_order(3, "Ugali", "Supu ya Nyama")
print(prep_station.orders)Terminal Output:
{3: ('Ugali', 'Supu ya Nyama')}7. Best Practices
Composition Example:
class SpiceBlender:
def grind(self) -> None:
print("Grinding spices...")
class StewPot:
def __init__(self):
self.blender = SpiceBlender()
def cook_stew(self) -> None:
self.blender.grind()
print("Simmering stew!")
pot = StewPot()
pot.cook_stew()Terminal Output:
Grinding spices...
Simmering stew!8. Project: African Restaurant System
@dataclass
class Order:
table: int
main_dish: str
soup: str
urgent: bool = False
class Kitchen:
def __init__(self):
self.orders: list[Order] = []
def prioritize_order(self, order: Order) -> None:
if order.urgent:
self.orders.insert(0, order)
else:
self.orders.append(order)
# Usage
order1 = Order(2, "Chapati", "Lentil Soup")
order2 = Order(5, "Ugali", "Beef Soup", urgent=True)
kitchen = Kitchen()
kitchen.prioritize_order(order1)
kitchen.prioritize_order(order2)
print([(o.table, o.urgent) for o in kitchen.orders])Terminal Output:
[(5, True), (2, False)]Type Hints:
order: Orderensures only Order objects acceptedlist[Order]specifies collection type
9. Exercises
Exercise 1 Solution:
Get Tani David’s stories in your inbox
Join Medium for free to get updates from this writer.
Here’s a modern OOP implementation using Python type hints, modeling an African restaurant preparing Ugali and Supu:
from abc import ABC, abstractmethod
from typing import List, Dict, Optional
class Dish(ABC):
"""Abstract base class for all dishes"""
def __init__(self, name: str, ingredients: Dict[str, str], cook_time: int):
self.name: str = name
self.ingredients: Dict[str, str] = ingredients
self.cook_time: int = cook_time # in minutes
@abstractmethod
def prepare(self) -> None:
"""Prepare the dish before cooking"""
pass
@abstractmethod
def cook(self) -> None:
"""Cook the dish"""
pass
def __repr__(self) -> str:
return f"{self.name} ({self.cook_time} mins)"
class Ugali(Dish):
"""Concrete class for Ugali dish"""
def __init__(self):
super().__init__(
name="Ugali",
ingredients={
"maize_flour": "2 cups",
"water": "4 cups"
},
cook_time=20
)
def prepare(self) -> None:
print("Mixing maize flour with cold water to make paste")
def cook(self) -> None:
print(f"Boiling water and stirring vigorously for {self.cook_time} minutes")
print("Forming thick consistency with wooden paddle")
class Supu(Dish):
"""Concrete class for vegetable/meat soup"""
def __init__(self, has_meat: bool = True):
super().__init__(
name="Supu ya Nyama" if has_meat else "Supu ya Mboga",
ingredients=self._get_ingredients(has_meat),
cook_time=45
)
self.has_meat: bool = has_meat
def _get_ingredients(self, has_meat: bool) -> Dict[str, str]:
base = {
"vegetables": "assorted (kale, carrots, onions)",
"spices": "cumin, coriander, pepper",
"water": "2 liters"
}
if has_meat:
base["meat"] = "1 kg beef"
return base
def prepare(self) -> None:
print("Chopping vegetables and meat" if self.has_meat else "Chopping vegetables")
print("Preparing spice mixture")
def cook(self) -> None:
print(f"Simmering {'meat and ' if self.has_meat else ''}vegetables")
print("Adding spices gradually while stirring")
class Meal:
"""Represents a complete meal combination"""
def __init__(self, dishes: List[Dish]):
self.dishes: List[Dish] = dishes
@property
def total_cooking_time(self) -> int:
return sum(dish.cook_time for dish in self.dishes)
def prepare_meal(self) -> None:
print("=== Meal Preparation ===")
for dish in self.dishes:
print(f"\nPreparing {dish.name}:")
dish.prepare()
dish.cook()
print(f"\nMeal ready in {self.total_cooking_time} minutes!")
class AfricanRestaurant:
"""Manages restaurant operations"""
def __init__(self):
self.menu: List[Dish] = [
Ugali(),
Supu(has_meat=True),
Supu(has_meat=False)
]
def prepare_combo_meal(self, dish_names: List[str]) -> Optional[Meal]:
selected_dishes = []
for name in dish_names:
dish = next((d for d in self.menu if d.name == name), None)
if dish:
selected_dishes.append(dish)
else:
print(f"⚠️ {name} not in menu!")
return None
return Meal(selected_dishes)
# Usage Example
if __name__ == "__main__":
# Initialize restaurant
nairobi_kitchen = AfricanRestaurant()
# Customer orders
meal = nairobi_kitchen.prepare_combo_meal(["Ugali", "Supu ya Nyama"])
# Prepare and serve meal
if meal:
print("🍴 Virunga Kitchen Order 🍴")
print(f"Menu: {[dish.name for dish in nairobi_kitchen.menu]}")
print(f"\nPreparing your meal with: {[d.name for d in meal.dishes]}")
meal.prepare_meal()Key OOP Concepts Demonstrated:
- Abstraction (
DishABC):
- Defines interface for all dishes
- Enforces implementation of
prepare()andcook()
2. Inheritance (Ugali & Supu):
- Inherit from
Dishbase class - Implement abstract methods differently
3. Encapsulation:
- Private method
_get_ingredients()inSupu - Protected attributes with type hints
4. Polymorphism:
Mealclass handles different dish types uniformly- Restaurant processes different dish implementations the same way
5. Composition:
Mealcomposed of multipleDishobjects- Restaurant contains menu items
Modern Python Features:
- Type hints for all methods and attributes
- Dictionary literals for ingredients
- Property decorator for calculated values
- Generics in type hints (
List[Dish])
Sample Output:
🍴 Virunga Kitchen Order 🍴
Menu: ['Ugali', 'Supu ya Nyama', 'Supu ya Mboga']
Preparing your meal with: ['Ugali', 'Supu ya Nyama']
=== Meal Preparation ===
Preparing Ugali:
Mixing maize flour with cold water to make paste
Boiling water and stirring vigorously for 20 minutes
Forming thick consistency with wooden paddle
Preparing Supu ya Nyama:
Chopping vegetables and meat
Preparing spice mixture
Simmering meat and vegetables
Adding spices gradually while stirring
Meal ready in 65 minutes!Exercises to Try:
- Add a new dish
Kanga-journéto the menu - Implement a
VegetarianMealsubclass with validation - Add portion sizes and calculate nutritional information
- Create a
OrderSystemclass that tracks multiple meals
Type Hints: Field types and method return type
Key Type Hint Takeaways:
- Always declare parameter types:
(self, name: str) - Specify return types:
-> Noneor-> float - Annotate variables:
price: float = 9.99 - Use typing module for collections:
List[Order]






