python
solutions
solutions.py🐍python
"""
Solutions for Module 09 - Object-Oriented Programming Exercises
These are reference solutions. Try to solve the exercises yourself first!
"""
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Optional
# =============================================================================
# Basic Classes
# =============================================================================
class Person:
"""A simple Person class demonstrating encapsulation."""
def __init__(self, name: str, age: int):
self._name = name
self._age = age
@property
def name(self) -> str:
return self._name
@property
def age(self) -> int:
return self._age
@age.setter
def age(self, value: int):
if value < 0:
raise ValueError("Age cannot be negative")
self._age = value
def greet(self) -> str:
return f"Hello, I'm {self._name} and I'm {self._age} years old."
def __str__(self) -> str:
return f"Person(name={self._name}, age={self._age})"
def __repr__(self) -> str:
return f"Person({self._name!r}, {self._age})"
class BankAccount:
"""Bank account demonstrating encapsulation."""
def __init__(self, owner: str, balance: float = 0.0):
self.owner = owner
self._balance = balance
self._transactions: list[tuple[str, float]] = []
@property
def balance(self) -> float:
return self._balance
def deposit(self, amount: float) -> None:
if amount <= 0:
raise ValueError("Deposit amount must be positive")
self._balance += amount
self._transactions.append(("deposit", amount))
def withdraw(self, amount: float) -> None:
if amount <= 0:
raise ValueError("Withdrawal amount must be positive")
if amount > self._balance:
raise ValueError("Insufficient funds")
self._balance -= amount
self._transactions.append(("withdraw", amount))
def get_statement(self) -> str:
lines = [f"Account Statement for {self.owner}"]
lines.append("-" * 30)
for tx_type, amount in self._transactions:
lines.append(f"{tx_type.capitalize()}: ${amount:.2f}")
lines.append("-" * 30)
lines.append(f"Balance: ${self._balance:.2f}")
return "\n".join(lines)
# =============================================================================
# Inheritance
# =============================================================================
class Animal(ABC):
"""Abstract base class for animals."""
def __init__(self, name: str):
self.name = name
@abstractmethod
def speak(self) -> str:
"""Return the sound this animal makes."""
pass
def __str__(self) -> str:
return f"{self.__class__.__name__}(name={self.name})"
class Dog(Animal):
"""Dog class inheriting from Animal."""
def __init__(self, name: str, breed: str = "Unknown"):
super().__init__(name)
self.breed = breed
def speak(self) -> str:
return f"{self.name} says: Woof!"
def fetch(self) -> str:
return f"{self.name} fetches the ball!"
class Cat(Animal):
"""Cat class inheriting from Animal."""
def __init__(self, name: str, indoor: bool = True):
super().__init__(name)
self.indoor = indoor
def speak(self) -> str:
return f"{self.name} says: Meow!"
def scratch(self) -> str:
return f"{self.name} scratches the furniture!"
# =============================================================================
# Polymorphism
# =============================================================================
class Shape(ABC):
"""Abstract base class for shapes."""
@abstractmethod
def area(self) -> float:
pass
@abstractmethod
def perimeter(self) -> float:
pass
class Rectangle(Shape):
"""Rectangle shape."""
def __init__(self, width: float, height: float):
self.width = width
self.height = height
def area(self) -> float:
return self.width * self.height
def perimeter(self) -> float:
return 2 * (self.width + self.height)
class Circle(Shape):
"""Circle shape."""
def __init__(self, radius: float):
self.radius = radius
def area(self) -> float:
import math
return math.pi * self.radius ** 2
def perimeter(self) -> float:
import math
return 2 * math.pi * self.radius
class Triangle(Shape):
"""Triangle shape."""
def __init__(self, a: float, b: float, c: float):
self.a = a
self.b = b
self.c = c
def area(self) -> float:
# Heron's formula
s = self.perimeter() / 2
import math
return math.sqrt(s * (s - self.a) * (s - self.b) * (s - self.c))
def perimeter(self) -> float:
return self.a + self.b + self.c
def total_area(shapes: list[Shape]) -> float:
"""Calculate total area of all shapes (polymorphism example)."""
return sum(shape.area() for shape in shapes)
# =============================================================================
# Composition
# =============================================================================
class Engine:
"""Engine component."""
def __init__(self, horsepower: int):
self.horsepower = horsepower
self.running = False
def start(self):
self.running = True
return "Engine started"
def stop(self):
self.running = False
return "Engine stopped"
class Car:
"""Car using composition with Engine."""
def __init__(self, make: str, model: str, horsepower: int):
self.make = make
self.model = model
self.engine = Engine(horsepower)
def start(self) -> str:
return f"{self.make} {self.model}: {self.engine.start()}"
def stop(self) -> str:
return f"{self.make} {self.model}: {self.engine.stop()}"
@property
def is_running(self) -> bool:
return self.engine.running
# =============================================================================
# Magic Methods (Dunder Methods)
# =============================================================================
class Vector:
"""2D Vector with operator overloading."""
def __init__(self, x: float, y: float):
self.x = x
self.y = y
def __add__(self, other: "Vector") -> "Vector":
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other: "Vector") -> "Vector":
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar: float) -> "Vector":
return Vector(self.x * scalar, self.y * scalar)
def __rmul__(self, scalar: float) -> "Vector":
return self * scalar
def __abs__(self) -> float:
import math
return math.sqrt(self.x ** 2 + self.y ** 2)
def __eq__(self, other: object) -> bool:
if not isinstance(other, Vector):
return NotImplemented
return self.x == other.x and self.y == other.y
def __repr__(self) -> str:
return f"Vector({self.x}, {self.y})"
def __str__(self) -> str:
return f"({self.x}, {self.y})"
def dot(self, other: "Vector") -> float:
"""Dot product."""
return self.x * other.x + self.y * other.y
# =============================================================================
# Dataclasses
# =============================================================================
@dataclass
class Product:
"""Product using dataclass."""
name: str
price: float
quantity: int = 0
category: str = "general"
@property
def total_value(self) -> float:
return self.price * self.quantity
def is_in_stock(self) -> bool:
return self.quantity > 0
@dataclass
class Order:
"""Order with multiple products."""
customer: str
items: list[Product] = field(default_factory=list)
def add_item(self, product: Product) -> None:
self.items.append(product)
@property
def total(self) -> float:
return sum(item.price * item.quantity for item in self.items)
# =============================================================================
# Class Methods and Static Methods
# =============================================================================
class Temperature:
"""Temperature class with alternative constructors."""
def __init__(self, celsius: float):
self.celsius = celsius
@classmethod
def from_fahrenheit(cls, fahrenheit: float) -> "Temperature":
"""Create Temperature from Fahrenheit."""
celsius = (fahrenheit - 32) * 5 / 9
return cls(celsius)
@classmethod
def from_kelvin(cls, kelvin: float) -> "Temperature":
"""Create Temperature from Kelvin."""
celsius = kelvin - 273.15
return cls(celsius)
@property
def fahrenheit(self) -> float:
return self.celsius * 9 / 5 + 32
@property
def kelvin(self) -> float:
return self.celsius + 273.15
@staticmethod
def is_freezing(temp_celsius: float) -> bool:
"""Check if temperature is at or below freezing."""
return temp_celsius <= 0
def __repr__(self) -> str:
return f"Temperature({self.celsius}°C)"
# =============================================================================
# Test Solutions
# =============================================================================
if __name__ == "__main__":
# Test Person
p = Person("Alice", 30)
assert p.greet() == "Hello, I'm Alice and I'm 30 years old."
# Test BankAccount
acc = BankAccount("Bob", 100)
acc.deposit(50)
acc.withdraw(30)
assert acc.balance == 120
# Test inheritance
dog = Dog("Buddy", "Labrador")
cat = Cat("Whiskers")
assert "Woof" in dog.speak()
assert "Meow" in cat.speak()
# Test polymorphism
shapes = [Rectangle(4, 5), Circle(3), Triangle(3, 4, 5)]
assert total_area(shapes) > 0
# Test composition
car = Car("Toyota", "Camry", 200)
car.start()
assert car.is_running
# Test Vector
v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1 + v2
assert v3.x == 4 and v3.y == 6
# Test dataclass
product = Product("Widget", 9.99, 10)
assert product.total_value == 99.9
# Test class methods
t1 = Temperature(20)
t2 = Temperature.from_fahrenheit(68)
assert abs(t1.celsius - t2.celsius) < 0.1
print("All OOP solutions verified! ✓")