Encapsulation, polymorphism, composition, and @property
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self._balance = balance # Single underscore: "private by convention"
def deposit(self, amount):
if amount <= 0:
raise ValueError("Deposit must be positive")
self._balance += amount
def get_balance(self):
return self._balance
account = BankAccount("Ada", 100)
account.deposit(50)
print(account.get_balance()) # 150
# You CAN still access _balance directly, but you shouldn't:
# print(account._balance) # Works, but breaks the convention_single_underscore = "private by convention" — please don't touch this from outside the class.__double_underscore = name mangling — Python renames it to _ClassName__attr to prevent accidental access in subclasses. Rarely needed.get_balance() and set_balance() methods, use @property to make attributes that look like normal access but run code behind the scenes:class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("Below absolute zero!")
self._celsius = value
@property
def fahrenheit(self):
return (self._celsius * 9/5) + 32
temp = Temperature(25)
print(temp.celsius) # 25 (calls the @property getter)
print(temp.fahrenheit) # 77.0 (computed on the fly)
temp.celsius = 100 # Calls the setter — validation runs
print(temp.fahrenheit) # 212.0.speak() method, we can call .speak() — we don't care what class it is.class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
class Robot:
def speak(self):
return "Beep boop."
# All three have .speak() — that's all that matters
def introduce(thing):
print(f"It says: {thing.speak()}")
for entity in [Dog(), Cat(), Robot()]:
introduce(entity) # Works for all three!class Engine:
def __init__(self, horsepower):
self.horsepower = horsepower
self.running = False
def start(self):
self.running = True
print(f"Engine ({self.horsepower}hp) started")
def stop(self):
self.running = False
print("Engine stopped")
class Car:
def __init__(self, make, engine):
self.make = make
self.engine = engine # Car HAS an engine (composition)
def start(self):
print(f"Starting {self.make}...")
self.engine.start()
def __str__(self):
status = "running" if self.engine.running else "off"
return f"{self.make} ({self.engine.horsepower}hp) — {status}"
v8 = Engine(450)
mustang = Car("Mustang", v8)
mustang.start()
print(mustang)Create a `Vehicle` class that: 1. Uses **composition** — takes an `Engine` object and stores it 2. Has a `_fuel` attribute with a `@property` and setter that clamps between 0 and capacity 3. Has a `fuel_percent` computed property 4. Has a `drive(distance)` method that checks if the engine is running and if there's enough fuel
loading editor...
Ask about OOP in Practice
◇
Hey! I'm your AI tutor.
Ask me anything about this lesson, or paste code you're stuck on.
AI tutor is a premium feature