OOP Intermediate
Abstract Classes
Enforced Interfaces
Your Shape class defines area() but Shape itself can't compute an area - only
Circle and Rectangle can. Abstract classes define methods that subclasses must
implement. You can't instantiate an abstract class directly.
Basic ABC
Create an abstract base class.
# Basic Abstract Classes with ABC
from abc import ABC, abstractmethod
# Abstract class - cannot be instantiated
class Animal(ABC):
"""
Abstract base class for animals.
ABC = Abstract Base Class
"""
def __init__(self, name):
self.name = name
@abstractmethod
def speak(self):
"""All animals must implement speak()."""
pass
@abstractmethod
def move(self):
"""All animals must implement move()."""
pass
# Concrete class - implements all abstract methods
class Dog(Animal):
"""Dog implements all abstract methods."""
def speak(self):
return f"{self.name} says: Woof!"
def move(self):
return f"{self.name} runs on four legs"
class Cat(Animal):
"""Cat implements all abstract methods."""
def speak(self):
return f"{self.name} says: Meow!"
def move(self):
return f"{self.name} walks gracefully"
class Bird(Animal):
"""Bird implements all abstract methods."""
def speak(self):
return f"{self.name} says: Tweet!"
def move(self):
return f"{self.name} flies through the air"
print("=== Basic Abstract Classes ===\n")
# Creating concrete instances
dog = Dog("Buddy")
cat = Cat("Whiskers")
bird = Bird("Tweety")
# Use them normally
animals = [dog, cat, bird]
for animal in animals:
print(f"{type(animal).__name__}:")
print(f" {animal.speak()}")
print(f" {animal.move()}")
print()
# Try to instantiate abstract class
print("--- Trying to instantiate abstract class ---")
try:
animal = Animal("Generic")
except TypeError as e:
print(f"Error: {e}")
print("Cannot instantiate abstract class!")
print("\n=== Key Points ===")
print("""
1. Import: from abc import ABC, abstractmethod
2. class MyClass(ABC): makes it abstract
3. @abstractmethod marks methods that MUST be overridden
4. Cannot create instance of abstract class
5. Subclass must implement ALL abstract methods
6. If subclass doesn't implement all, it's also abstract
""")
# Demonstrate incomplete implementation
print("\n--- Incomplete implementation ---")
class Fish(Animal):
"""Fish only implements speak - incomplete!"""
def speak(self):
return f"{self.name} says: Blub!"
# Missing move() method!
try:
fish = Fish("Nemo")
except TypeError as e:
print(f"Error: {e}")
print("Fish is still abstract because move() is missing!")
Abstract base class for animals.
6class Animal(ABC): #?animalabc7 """8 Abstract base class for animals.9 ABC = Abstract Base Class10 """11 12 def __init__(self, name): #?abcinit13 self.name = name14 15 @abstractmethod #?abstractdecorator16 def speak(self): #?abstractspeak17 """All animals must implement speak()."""18 pass #?passabstract19 20 @abstractmethod21 def move(self): #?abstractmove22 """All animals must implement move()."""23 pass242526# Concrete class - implements all abstract methods #?concreteclass27class Dog(Animal): #?dogclass28 """Dog implements all abstract methods."""29 30 def speak(self): #?dogspeak31 return f"{self.name} says: Woof!"32 33 def move(self): #?dogmove34 return f"{self.name} runs on four legs"353637class Cat(Animal): #?catclass38 """Cat implements all abstract methods."""39 40 def speak(self): #?catspeak41 return f"{self.name} says: Meow!"42 43 def move(self): #?catmove44 return f"{self.name} walks gracefully"454647class Bird(Animal): #?birdclass48 """Bird implements all abstract methods."""49 50 def speak(self): #?birdspeak51 return f"{self.name} says: Tweet!"52 53 def move(self): #?birdmove54 return f"{self.name} flies through the air"555657print("=== Basic Abstract Classes ===\n")5859# Creating concrete instances #?createinstances60dog = Dog("Buddy") #?createdog61cat = Cat("Whiskers") #?createcatoutput=== Basic Abstract Classes ===self.name ← Buddy
pass 1 of 312def __init__(self⟨Dog A⟩, nameBuddy): #?abcinit13 self.name→ Buddy = nameBuddyAll 3 passes — pass 1 is the card above pass selfnameself.name1 ⟨Dog A⟩ Buddy Buddy 2 ⟨Cat B⟩ Whiskers Whiskers 3 ⟨Bird C⟩ Tweety Tweety dog ← ⟨Dog A⟩
59# Creating concrete instances #?createinstances60dog→ ⟨Dog A⟩ = Dog("Buddy") #?createdog61cat = Cat("Whiskers") #?createcat62bird = Bird("Tweety") #?createbirdcat ← ⟨Cat B⟩
60dog = Dog("Buddy") #?createdog61cat→ ⟨Cat B⟩ = Cat("Whiskers") #?createcat62bird = Bird("Tweety") #?createbirdbird ← ⟨Bird C⟩, animals ← [⟨Dog A⟩, ⟨Cat B⟩, ⟨Bird C⟩]
61cat = Cat("Whiskers") #?createcat62bird→ ⟨Bird C⟩ = Bird("Tweety") #?createbird6364# Use them normally #?useinstances65animals→ [⟨Dog A⟩, ⟨Cat B⟩, ⟨Bird C⟩] = [dog⟨Dog A⟩, cat⟨Cat B⟩, bird⟨Bird C⟩] #?animalslistfor animal in animals: #?iterateanimals
pass 1 of 367for animal⟨Dog A⟩ in animals[⟨Dog A⟩, ⟨Cat B⟩, ⟨Bird C⟩]: #?iterateanimals68 print(f"{type(animal⟨Dog A⟩).__name__}:")69 print(f" {animal⟨Dog A⟩.speak()}") #?callspeak70 print(f" {animal.move()}") #?callmoveoutputDog:All 3 passes — pass 1 is the card above pass animalselfself.name1 ⟨Dog A⟩ ⟨Dog A⟩ Buddy 2 ⟨Cat B⟩ ⟨Cat B⟩ Whiskers 3 ⟨Bird C⟩ ⟨Bird C⟩ Tweety def speak(self): #?dogspeak
30def speak(self⟨Dog A⟩): #?dogspeak31 return f"{self.nameBuddy} says: Woof!"print(f" {animal.speak()}") #?callspeak
68print(f"{type(animal).__name__}:")69print(f" {animal⟨Dog A⟩.speak()}") #?callspeak70print(f" {animal⟨Dog A⟩.move()}") #?callmove71print()output Buddy says: Woof!def move(self): #?dogmove
33def move(self⟨Dog A⟩): #?dogmove34 return f"{self.nameBuddy} runs on four legs"print(f" {animal.move()}") #?callmove
69print(f" {animal.speak()}") #?callspeak70print(f" {animal⟨Dog A⟩.move()}") #?callmove71print()output Buddy runs on four legsdef speak(self): #?catspeak
40def speak(self⟨Cat B⟩): #?catspeak41 return f"{self.nameWhiskers} says: Meow!"print(f" {animal.speak()}") #?callspeak
68print(f"{type(animal).__name__}:")69print(f" {animal⟨Cat B⟩.speak()}") #?callspeak70print(f" {animal⟨Cat B⟩.move()}") #?callmove71print()output Whiskers says: Meow!def move(self): #?catmove
43def move(self⟨Cat B⟩): #?catmove44 return f"{self.nameWhiskers} walks gracefully"print(f" {animal.move()}") #?callmove
69print(f" {animal.speak()}") #?callspeak70print(f" {animal⟨Cat B⟩.move()}") #?callmove71print()output Whiskers walks gracefullydef speak(self): #?birdspeak
50def speak(self⟨Bird C⟩): #?birdspeak51 return f"{self.nameTweety} says: Tweet!"print(f" {animal.speak()}") #?callspeak
68print(f"{type(animal).__name__}:")69print(f" {animal⟨Bird C⟩.speak()}") #?callspeak70print(f" {animal⟨Bird C⟩.move()}") #?callmove71print()output Tweety says: Tweet!def move(self): #?birdmove
53def move(self⟨Bird C⟩): #?birdmove54 return f"{self.nameTweety} flies through the air"print(f" {animal.move()}") #?callmove
69print(f" {animal.speak()}") #?callspeak70print(f" {animal⟨Bird C⟩.move()}") #?callmove71print()output Tweety flies through the airprint("--- Trying to instantiate abstract class ---")
73# Try to instantiate abstract class #?tryabstract74print("--- Trying to instantiate abstract class ---")75try:output--- Trying to instantiate abstract class ---except TypeError as e: #?typeerror
76 animal = Animal("Generic") #?instantiateabstract77except TypeError as e: #?typeerror78 print(f"Error: {eCan't instantiate abstract class Animal without an implementation for abstract methods 'move', 'speak'}")79 print("Cannot instantiate abstract class!")outputError: Can't instantiate abstract class Animal without an implementation for abstract methods 'move', 'speak' Cannot instantiate abstract class!print(" === Key Points ===")
81print("\n=== Key Points ===")82print("""831. Import: from abc import ABC, abstractmethod842. class MyClass(ABC): makes it abstract853. @abstractmethod marks methods that MUST be overridden864. Cannot create instance of abstract class875. Subclass must implement ALL abstract methods886. If subclass doesn't implement all, it's also abstract89""")9091# Demonstrate incomplete implementation #?incomplete92print("\n--- Incomplete implementation ---")9394class Fish(Animal): #?fishincomplete95 """Fish only implements speak - incomplete!"""output === Key Points === 1. Import: from abc import ABC, abstractmethod 2. class MyClass(ABC): makes it abstract 3. @abstractmethod marks methods that MUST be overridden 4. Cannot create instance of abstract class 5. Subclass must implement ALL abstract methods 6. If subclass doesn't implement all, it's also abstract --- Incomplete implementation ---except TypeError as e: #?fisherror
103 fish = Fish("Nemo") #?tryfish104except TypeError as e: #?fisherror105 print(f"Error: {eCan't instantiate abstract class Fish without an implementation for abstract method 'move'}")106 print("Fish is still abstract because move() is missing!")outputError: Can't instantiate abstract class Fish without an implementation for abstract method 'move' Fish is still abstract because move() is missing!
from abc import ABC, abstractmethod. Class inherits from ABC.
Abstract methods
Methods that subclasses must implement.
# Defining and Implementing Abstract Methods
from abc import ABC, abstractmethod
# Abstract class with multiple abstract methods
class Shape(ABC):
"""
Abstract shape class.
Defines what all shapes must do.
"""
@abstractmethod
def area(self) -> float:
"""Calculate and return the area."""
pass
@abstractmethod
def perimeter(self) -> float:
"""Calculate and return the perimeter."""
pass
@abstractmethod
def name(self) -> str:
"""Return the name of the shape."""
pass
class Circle(Shape):
"""Concrete implementation of Shape for circles."""
PI = 3.14159
def __init__(self, radius):
self.radius = radius
def area(self) -> float:
return Circle.PI * self.radius ** 2
def perimeter(self) -> float:
return 2 * Circle.PI * self.radius
def name(self) -> str:
return "Circle"
class Rectangle(Shape):
"""Concrete implementation of Shape for rectangles."""
def __init__(self, width, height):
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)
def name(self) -> str:
return "Rectangle"
class Triangle(Shape):
"""Concrete implementation for triangles."""
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def area(self) -> float:
# Heron's formula
s = (self.a + self.b + self.c) / 2
return (s * (s - self.a) * (s - self.b) * (s - self.c)) ** 0.5
def perimeter(self) -> float:
return self.a + self.b + self.c
def name(self) -> str:
return "Triangle"
# Utility function that works with any Shape
def describe_shape(shape: Shape):
"""Works with any Shape - relies on abstract methods."""
print(f"Shape: {shape.name()}")
print(f" Area: {shape.area():.2f}")
print(f" Perimeter: {shape.perimeter():.2f}")
def total_area(shapes: list) -> float:
"""Calculate total area of all shapes."""
return sum(shape.area() for shape in shapes)
print("=== Abstract Methods ===\n")
# Create shapes
circle = Circle(5)
rectangle = Rectangle(4, 6)
triangle = Triangle(3, 4, 5)
shapes = [circle, rectangle, triangle]
# Describe each shape
for shape in shapes:
describe_shape(shape)
print()
# Calculate total area
total = total_area(shapes)
print(f"Total area of all shapes: {total:.2f}")
# Abstract method signature documentation
print("\n--- Method Signatures ---")
print("Shape abstract methods define the interface:")
print(f" area() -> float")
print(f" perimeter() -> float")
print(f" name() -> str")
# Type checking with isinstance
print("\n--- Type Checking ---")
for shape in shapes:
print(f"{shape.name()} is a Shape: {isinstance(shape, Shape)}")
print("\n=== Why Abstract Methods? ===")
print("""
1. Enforce Contract:
- Subclasses MUST implement these methods
- Compile-time-like safety at class definition
2. Document Interface:
- Clear what methods are required
- Type hints show expected return types
3. Enable Polymorphism:
- Functions can depend on abstract methods
- Works with any concrete implementation
4. Design Patterns:
- Strategy pattern
- Template method pattern
- Factory pattern
""")
# Defining and Implementing Abstract Methods
from abc import ABC, abstractmethod
# Abstract class with multiple abstract methods
class Shape(ABC):
"""
Abstract shape class.
Defines what all shapes must do.
"""
@abstractmethod
def area(self) -> float:
"""Calculate and return the area."""
pass
@abstractmethod
def perimeter(self) -> float:
"""Calculate and return the perimeter."""
pass
@abstractmethod
def name(self) -> str:
"""Return the name of the shape."""
pass
class Circle(Shape):
"""Concrete implementation of Shape for circles."""
PI = 3.14159
def __init__(self, radius):
self.radius = radius
def area(self) -> float:
return Circle.PI * self.radius ** 2
def perimeter(self) -> float:
return 2 * Circle.PI * self.radius
def name(self) -> str:
return "Circle"
class Rectangle(Shape):
"""Concrete implementation of Shape for rectangles."""
def __init__(self, width, height):
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)
def name(self) -> str:
return "Rectangle"
class Triangle(Shape):
"""Concrete implementation for triangles."""
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def area(self) -> float:
# Heron's formula
s = (self.a + self.b + self.c) / 2
return (s * (s - self.a) * (s - self.b) * (s - self.c)) ** 0.5
def perimeter(self) -> float:
return self.a + self.b + self.c
def name(self) -> str:
return "Triangle"
# Utility function that works with any Shape
def describe_shape(shape: Shape):
"""Works with any Shape - relies on abstract methods."""
print(f"Shape: {shape.name()}")
print(f" Area: {shape.area():.2f}")
print(f" Perimeter: {shape.perimeter():.2f}")
def total_area(shapes: list) -> float:
"""Calculate total area of all shapes."""
return sum(shape.area() for shape in shapes)
print("=== Abstract Methods ===\n")
# Create shapes
circle = Circle(5)
rectangle = Rectangle(4, 6)
triangle = Triangle(3, 4, 5)
shapes = [circle, rectangle]
# Describe each shape
for shape in shapes:
describe_shape(shape)
print()
# Calculate total area
total = total_area(shapes)
print(f"Total area of all shapes: {total:.2f}")
# Abstract method signature documentation
print("\n--- Method Signatures ---")
print("Shape abstract methods define the interface:")
print(f" area() -> float")
print(f" perimeter() -> float")
print(f" name() -> str")
# Type checking with isinstance
print("\n--- Type Checking ---")
for shape in shapes:
print(f"{shape.name()} is a Shape: {isinstance(shape, Shape)}")
print("\n=== Why Abstract Methods? ===")
print("""
1. Enforce Contract:
- Subclasses MUST implement these methods
- Compile-time-like safety at class definition
2. Document Interface:
- Clear what methods are required
- Type hints show expected return types
3. Enable Polymorphism:
- Functions can depend on abstract methods
- Works with any concrete implementation
4. Design Patterns:
- Strategy pattern
- Template method pattern
- Factory pattern
""")
# Defining and Implementing Abstract Methods
from abc import ABC, abstractmethod
# Abstract class with multiple abstract methods
class Shape(ABC):
"""
Abstract shape class.
Defines what all shapes must do.
"""
@abstractmethod
def area(self) -> float:
"""Calculate and return the area."""
pass
@abstractmethod
def perimeter(self) -> float:
"""Calculate and return the perimeter."""
pass
@abstractmethod
def name(self) -> str:
"""Return the name of the shape."""
pass
class Circle(Shape):
"""Concrete implementation of Shape for circles."""
PI = 3.14159
def __init__(self, radius):
self.radius = radius
def area(self) -> float:
return Circle.PI * self.radius ** 2
def perimeter(self) -> float:
return 2 * Circle.PI * self.radius
def name(self) -> str:
return "Circle"
class Rectangle(Shape):
"""Concrete implementation of Shape for rectangles."""
def __init__(self, width, height):
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)
def name(self) -> str:
return "Rectangle"
class Triangle(Shape):
"""Concrete implementation for triangles."""
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def area(self) -> float:
# Heron's formula
s = (self.a + self.b + self.c) / 2
return (s * (s - self.a) * (s - self.b) * (s - self.c)) ** 0.5
def perimeter(self) -> float:
return self.a + self.b + self.c
def name(self) -> str:
return "Triangle"
# Utility function that works with any Shape
def describe_shape(shape: Shape):
"""Works with any Shape - relies on abstract methods."""
print(f"Shape: {shape.name()}")
print(f" Area: {shape.area():.2f}")
print(f" Perimeter: {shape.perimeter():.2f}")
def total_area(shapes: list) -> float:
"""Calculate total area of all shapes."""
return sum(shape.area() for shape in shapes)
print("=== Abstract Methods ===\n")
# Create shapes
circle = Circle(5)
rectangle = Rectangle(4, 6)
triangle = Triangle(3, 4, 5)
shapes = [triangle]
# Describe each shape
for shape in shapes:
describe_shape(shape)
print()
# Calculate total area
total = total_area(shapes)
print(f"Total area of all shapes: {total:.2f}")
# Abstract method signature documentation
print("\n--- Method Signatures ---")
print("Shape abstract methods define the interface:")
print(f" area() -> float")
print(f" perimeter() -> float")
print(f" name() -> str")
# Type checking with isinstance
print("\n--- Type Checking ---")
for shape in shapes:
print(f"{shape.name()} is a Shape: {isinstance(shape, Shape)}")
print("\n=== Why Abstract Methods? ===")
print("""
1. Enforce Contract:
- Subclasses MUST implement these methods
- Compile-time-like safety at class definition
2. Document Interface:
- Clear what methods are required
- Type hints show expected return types
3. Enable Polymorphism:
- Functions can depend on abstract methods
- Works with any concrete implementation
4. Design Patterns:
- Strategy pattern
- Template method pattern
- Factory pattern
""")
PI ← (empty)
6class Shape(ABC): #?shapeclass7 """8 Abstract shape class.9 Defines what all shapes must do.10 """11 12 @abstractmethod13 def area(self) -> float: #?areamethod14 """Calculate and return the area."""15 pass16 17 @abstractmethod18 def perimeter(self) -> float: #?perimetermethod19 """Calculate and return the perimeter."""20 pass21 22 @abstractmethod23 def name(self) -> str: #?namemethod24 """Return the name of the shape."""25 pass262728class Circle(Shape): #?circleclass29 """Concrete implementation of Shape for circles."""30 31 PI→ (empty) = 3.14159 #?piconstant32 33 def __init__(self, radius): #?circleinit34 self.radius = radius35 36 def area(self) -> float: #?circlearea37 return Circle.PI * self.radius ** 238 39 def perimeter(self) -> float: #?circleperimeter40 return 2 * Circle.PI * self.radius41 42 def name(self) -> str: #?circlename43 return "Circle"444546class Rectangle(Shape): #?rectangleclass47 """Concrete implementation of Shape for rectangles."""48 49 def __init__(self, width, height): #?rectangleinit50 self.width = width51 self.height = height52 53 def area(self) -> float: #?rectanglearea54 return self.width * self.height55 56 def perimeter(self) -> float: #?rectangleperimeter57 return 2 * (self.width + self.height)58 59 def name(self) -> str: #?rectanglename60 return "Rectangle"616263class Triangle(Shape): #?triangleclass64 """Concrete implementation for triangles."""65 66 def __init__(self, a, b, c): #?triangleinit67 self.a = a68 self.b = b69 self.c = c70 71 def area(self) -> float: #?trianglearea72 # Heron's formula #?herons73 s = (self.a + self.b + self.c) / 274 return (s * (s - self.a) * (s - self.b) * (s - self.c)) ** 0.575 76 def perimeter(self) -> float: #?triangleperimeter77 return self.a + self.b + self.c78 79 def name(self) -> str: #?trianglename80 return "Triangle"818283# Utility function that works with any Shape #?utilfunc84def describe_shape(shape: Shape): #?describeshape85 """Works with any Shape - relies on abstract methods."""86 print(f"Shape: {shape.name()}") #?callname87 print(f" Area: {shape.area():.2f}") #?callarea88 print(f" Perimeter: {shape.perimeter():.2f}") #?callperimeter899091def total_area(shapes: list) -> float: #?totalarea92 """Calculate total area of all shapes."""93 return sum(shape.area() for shape in shapes) #?sumarea949596print("=== Abstract Methods ===\n")9798# Create shapes #?createshapes99circle = Circle(5) #?createcircle100rectangle = Rectangle(4, 6) #?createrectangleoutput=== Abstract Methods ===self.radius ← 5
33def __init__(self⟨Circle A⟩, radius5): #?circleinit34 self.radius→ 5 = radius5circle ← ⟨Circle A⟩
98# Create shapes #?createshapes99circle→ ⟨Circle A⟩ = Circle(5) #?createcircle100rectangle = Rectangle(4, 6) #?createrectangle101triangle = Triangle(3, 4, 5) #?createtriangleself.width ← 4, self.height ← 6
49def __init__(self⟨Rectangle B⟩, width4, height6): #?rectangleinit50 self.width→ 4 = width451 self.height→ 6 = height6rectangle ← ⟨Rectangle B⟩
99circle = Circle(5) #?createcircle100rectangle→ ⟨Rectangle B⟩ = Rectangle(4, 6) #?createrectangle101triangle = Triangle(3, 4, 5) #?createtriangleself.a ← 3, self.b ← 4, self.c ← 5
66def __init__(self⟨Triangle C⟩, a3, b4, c5): #?triangleinit67 self.a→ 3 = a368 self.b→ 4 = b469 self.c→ 5 = c5triangle ← ⟨Triangle C⟩, shapes ← [⟨Circle A⟩, ⟨Rectangle B⟩, ⟨Triangle C⟩]
100rectangle = Rectangle(4, 6) #?createrectangle101triangle→ ⟨Triangle C⟩ = Triangle(3, 4, 5) #?createtriangle102103shapes→ [⟨Circle A⟩, ⟨Rectangle B⟩, ⟨Triangle C⟩] = [circle⟨Circle A⟩, rectangle⟨Rectangle B⟩, triangle⟨Triangle C⟩] #?shapeslist104#@shapes=[circle, rectangle], [triangle], [circle, rectangle, triangle]for shape in shapes: #?iterateshapes
pass 1 of 3106# Describe each shape #?describeall107for shape⟨Circle A⟩ in shapes[⟨Circle A⟩, ⟨Rectangle B⟩, ⟨Triangle C⟩]: #?iterateshapes108 describe_shape(shape⟨Circle A⟩) #?calldescribe109 print()All 3 passes — pass 1 is the card above pass shapeself1 ⟨Circle A⟩ ⟨Circle A⟩ 2 ⟨Rectangle B⟩ ⟨Rectangle B⟩ 3 ⟨Triangle C⟩ ⟨Triangle C⟩ def describe_shape(shape: Shape): #?describeshape
pass 1 of 383# Utility function that works with any Shape #?utilfunc84def describe_shape(shape⟨Circle A⟩: Shape): #?describeshape85 """Works with any Shape - relies on abstract methods."""86 print(f"Shape: {shape⟨Circle A⟩.name()}") #?callname87 print(f" Area: {shape.area():.2f}") #?callareaAll 3 passes — pass 1 is the card above pass shapeself1 ⟨Circle A⟩ ⟨Circle A⟩ 2 ⟨Rectangle B⟩ ⟨Rectangle B⟩ 3 ⟨Triangle C⟩ ⟨Triangle C⟩ def name(self) -> str: #?circlename
pass 1 of 242def name(self⟨Circle A⟩) -> str: #?circlename43 return "Circle"print(f"Shape: {shape.name()}") #?callname
85"""Works with any Shape - relies on abstract methods."""86print(f"Shape: {shape⟨Circle A⟩.name()}") #?callname87print(f" Area: {shape⟨Circle A⟩.area():.2f}") #?callarea88print(f" Perimeter: {shape.perimeter():.2f}") #?callperimeteroutputShape: Circledef area(self) -> float: #?circlearea
pass 1 of 236def area(self⟨Circle A⟩) -> float: #?circlearea37 return Circle.PI3.14159 * self.radius5 ** 2print(f" Area: {shape.area():.2f}") #?callarea
86print(f"Shape: {shape.name()}") #?callname87print(f" Area: {shape⟨Circle A⟩.area():.2f}") #?callarea88print(f" Perimeter: {shape⟨Circle A⟩.perimeter():.2f}") #?callperimeteroutput Area: 78.54def perimeter(self) -> float: #?circleperimeter
39def perimeter(self⟨Circle A⟩) -> float: #?circleperimeter40 return 2 * Circle.PI3.14159 * self.radius5print(f" Perimeter: {shape.perimeter():.2f}") #?callperimeter
87print(f" Area: {shape.area():.2f}") #?callarea88print(f" Perimeter: {shape⟨Circle A⟩.perimeter():.2f}") #?callperimeteroutput Perimeter: 31.42describe_shape(shape) #?calldescribe
107for shape in shapes: #?iterateshapes108 describe_shape(shape⟨Circle A⟩) #?calldescribe109 print()def name(self) -> str: #?rectanglename
pass 1 of 259def name(self⟨Rectangle B⟩) -> str: #?rectanglename60 return "Rectangle"print(f"Shape: {shape.name()}") #?callname
85"""Works with any Shape - relies on abstract methods."""86print(f"Shape: {shape⟨Rectangle B⟩.name()}") #?callname87print(f" Area: {shape⟨Rectangle B⟩.area():.2f}") #?callarea88print(f" Perimeter: {shape.perimeter():.2f}") #?callperimeteroutputShape: Rectangledef area(self) -> float: #?rectanglearea
pass 1 of 253def area(self⟨Rectangle B⟩) -> float: #?rectanglearea54 return self.width4 * self.height6print(f" Area: {shape.area():.2f}") #?callarea
86print(f"Shape: {shape.name()}") #?callname87print(f" Area: {shape⟨Rectangle B⟩.area():.2f}") #?callarea88print(f" Perimeter: {shape⟨Rectangle B⟩.perimeter():.2f}") #?callperimeteroutput Area: 24.00def perimeter(self) -> float: #?rectangleperimeter
56def perimeter(self⟨Rectangle B⟩) -> float: #?rectangleperimeter57 return 2 * (self.width4 + self.height6)print(f" Perimeter: {shape.perimeter():.2f}") #?callperimeter
87print(f" Area: {shape.area():.2f}") #?callarea88print(f" Perimeter: {shape⟨Rectangle B⟩.perimeter():.2f}") #?callperimeteroutput Perimeter: 20.00describe_shape(shape) #?calldescribe
107for shape in shapes: #?iterateshapes108 describe_shape(shape⟨Rectangle B⟩) #?calldescribe109 print()def name(self) -> str: #?trianglename
pass 1 of 279def name(self⟨Triangle C⟩) -> str: #?trianglename80 return "Triangle"print(f"Shape: {shape.name()}") #?callname
85"""Works with any Shape - relies on abstract methods."""86print(f"Shape: {shape⟨Triangle C⟩.name()}") #?callname87print(f" Area: {shape⟨Triangle C⟩.area():.2f}") #?callarea88print(f" Perimeter: {shape.perimeter():.2f}") #?callperimeteroutputShape: Triangles ← 6.0
pass 1 of 271def area(self⟨Triangle C⟩) -> float: #?trianglearea72 # Heron's formula #?herons73 s→ 6.0 = (self.a3 + self.b4 + self.c5) / 274 return (s6.0 * (s - self.a3) * (s - self.b4) * (s - self.c5)) ** 0.5print(f" Area: {shape.area():.2f}") #?callarea
86print(f"Shape: {shape.name()}") #?callname87print(f" Area: {shape⟨Triangle C⟩.area():.2f}") #?callarea88print(f" Perimeter: {shape⟨Triangle C⟩.perimeter():.2f}") #?callperimeteroutput Area: 6.00def perimeter(self) -> float: #?triangleperimeter
76def perimeter(self⟨Triangle C⟩) -> float: #?triangleperimeter77 return self.a3 + self.b4 + self.c5print(f" Perimeter: {shape.perimeter():.2f}") #?callperimeter
87print(f" Area: {shape.area():.2f}") #?callarea88print(f" Perimeter: {shape⟨Triangle C⟩.perimeter():.2f}") #?callperimeteroutput Perimeter: 12.00describe_shape(shape) #?calldescribe
107for shape in shapes: #?iterateshapes108 describe_shape(shape⟨Triangle C⟩) #?calldescribe109 print()total = total_area(shapes) #?gettotal
111# Calculate total area #?calctotal112total = total_area(shapes[⟨Circle A⟩, ⟨Rectangle B⟩, ⟨Triangle C⟩]) #?gettotal113print(f"Total area of all shapes: {total:.2f}")def total_area(shapes: list) -> float: #?totalarea
91def total_area(shapes[⟨Circle A⟩, ⟨Rectangle B⟩, ⟨Triangle C⟩]: list) -> float: #?totalarea92 """Calculate total area of all shapes."""93 return sum(shape.area() for shape in shapes[⟨Circle A⟩, ⟨Rectangle B⟩, ⟨Triangle C⟩]) #?sumareadef area(self) -> float: #?circlearea
pass 2 of 236def area(self⟨Circle A⟩) -> float: #?circlearea37 return Circle.PI3.14159 * self.radius5 ** 2def area(self) -> float: #?rectanglearea
pass 2 of 253def area(self⟨Rectangle B⟩) -> float: #?rectanglearea54 return self.width4 * self.height6s ← 6.0
pass 2 of 271def area(self⟨Triangle C⟩) -> float: #?trianglearea72 # Heron's formula #?herons73 s→ 6.0 = (self.a3 + self.b4 + self.c5) / 274 return (s6.0 * (s - self.a3) * (s - self.b4) * (s - self.c5)) ** 0.5total ← 108.53975
111# Calculate total area #?calctotal112total→ 108.53975 = total_area(shapes[⟨Circle A⟩, ⟨Rectangle B⟩, ⟨Triangle C⟩]) #?gettotal113print(f"Total area of all shapes: {total108.53975:.2f}")114115# Abstract method signature documentation #?signaturedemo116print("\n--- Method Signatures ---")117print("Shape abstract methods define the interface:")118print(f" area() -> float")119print(f" perimeter() -> float")120print(f" name() -> str")121122# Type checking with isinstance #?typechecking123print("\n--- Type Checking ---")124for shape in shapes:outputTotal area of all shapes: 108.54 --- Method Signatures --- Shape abstract methods define the interface: area() -> float perimeter() -> float name() -> str --- Type Checking ---for shape in shapes:
pass 1 of 3123print("\n--- Type Checking ---")124for shape⟨Circle A⟩ in shapes[⟨Circle A⟩, ⟨Rectangle B⟩, ⟨Triangle C⟩]:125 print(f"{shape⟨Circle A⟩.name()} is a Shape: {isinstance(shape, Shape<class '__main__.Shape'>)}") #?isinstanceshapeAll 3 passes — pass 1 is the card above pass shapeself1 ⟨Circle A⟩ ⟨Circle A⟩ 2 ⟨Rectangle B⟩ ⟨Rectangle B⟩ 3 ⟨Triangle C⟩ ⟨Triangle C⟩ def name(self) -> str: #?circlename
pass 2 of 242def name(self⟨Circle A⟩) -> str: #?circlename43 return "Circle"print(f"{shape.name()} is a Shape: {isinstance(shape, Shape)}") #?isin…
124for shape in shapes:125 print(f"{shape⟨Circle A⟩.name()} is a Shape: {isinstance(shape, Shape<class '__main__.Shape'>)}") #?isinstanceshapeoutputCircle is a Shape: Truedef name(self) -> str: #?rectanglename
pass 2 of 259def name(self⟨Rectangle B⟩) -> str: #?rectanglename60 return "Rectangle"print(f"{shape.name()} is a Shape: {isinstance(shape, Shape)}") #?isin…
124for shape in shapes:125 print(f"{shape⟨Rectangle B⟩.name()} is a Shape: {isinstance(shape, Shape<class '__main__.Shape'>)}") #?isinstanceshapeoutputRectangle is a Shape: Truedef name(self) -> str: #?trianglename
pass 2 of 279def name(self⟨Triangle C⟩) -> str: #?trianglename80 return "Triangle"print(f"{shape.name()} is a Shape: {isinstance(shape, Shape)}") #?isin…
124for shape in shapes:125 print(f"{shape⟨Triangle C⟩.name()} is a Shape: {isinstance(shape, Shape<class '__main__.Shape'>)}") #?isinstanceshapeoutputTriangle is a Shape: Trueprint(" === Why Abstract Methods? ===")
127print("\n=== Why Abstract Methods? ===")128print("""1291. Enforce Contract:130 - Subclasses MUST implement these methods131 - Compile-time-like safety at class definition1321332. Document Interface:134 - Clear what methods are required135 - Type hints show expected return types1361373. Enable Polymorphism:138 - Functions can depend on abstract methods139 - Works with any concrete implementation1401414. Design Patterns:142 - Strategy pattern143 - Template method pattern144 - Factory pattern145""")output === Why Abstract Methods? === 1. Enforce Contract: - Subclasses MUST implement these methods - Compile-time-like safety at class definition 2. Document Interface: - Clear what methods are required - Type hints show expected return types 3. Enable Polymorphism: - Functions can depend on abstract methods - Works with any concrete implementation 4. Design Patterns: - Strategy pattern - Template method pattern - Factory pattern
PI ← (empty)
6class Shape(ABC):7 """8 Abstract shape class.9 Defines what all shapes must do.10 """11 12 @abstractmethod13 def area(self) -> float:14 """Calculate and return the area."""15 pass16 17 @abstractmethod18 def perimeter(self) -> float:19 """Calculate and return the perimeter."""20 pass21 22 @abstractmethod23 def name(self) -> str:24 """Return the name of the shape."""25 pass262728class Circle(Shape):29 """Concrete implementation of Shape for circles."""30 31 PI→ (empty) = 3.1415932 33 def __init__(self, radius):34 self.radius = radius35 36 def area(self) -> float:37 return Circle.PI * self.radius ** 238 39 def perimeter(self) -> float:40 return 2 * Circle.PI * self.radius41 42 def name(self) -> str:43 return "Circle"444546class Rectangle(Shape):47 """Concrete implementation of Shape for rectangles."""48 49 def __init__(self, width, height):50 self.width = width51 self.height = height52 53 def area(self) -> float:54 return self.width * self.height55 56 def perimeter(self) -> float:57 return 2 * (self.width + self.height)58 59 def name(self) -> str:60 return "Rectangle"616263class Triangle(Shape):64 """Concrete implementation for triangles."""65 66 def __init__(self, a, b, c):67 self.a = a68 self.b = b69 self.c = c70 71 def area(self) -> float:72 # Heron's formula73 s = (self.a + self.b + self.c) / 274 return (s * (s - self.a) * (s - self.b) * (s - self.c)) ** 0.575 76 def perimeter(self) -> float:77 return self.a + self.b + self.c78 79 def name(self) -> str:80 return "Triangle"818283# Utility function that works with any Shape84def describe_shape(shape: Shape):85 """Works with any Shape - relies on abstract methods."""86 print(f"Shape: {shape.name()}")87 print(f" Area: {shape.area():.2f}")88 print(f" Perimeter: {shape.perimeter():.2f}")899091def total_area(shapes: list) -> float:92 """Calculate total area of all shapes."""93 return sum(shape.area() for shape in shapes)949596print("=== Abstract Methods ===\n")9798# Create shapes99circle = Circle(5)100rectangle = Rectangle(4, 6)output=== Abstract Methods ===self.radius ← 5
33def __init__(self⟨Circle A⟩, radius5):34 self.radius→ 5 = radius5circle ← ⟨Circle A⟩
98# Create shapes99circle→ ⟨Circle A⟩ = Circle(5)100rectangle = Rectangle(4, 6)101triangle = Triangle(3, 4, 5)self.width ← 4, self.height ← 6
49def __init__(self⟨Rectangle B⟩, width4, height6):50 self.width→ 4 = width451 self.height→ 6 = height6rectangle ← ⟨Rectangle B⟩
99circle = Circle(5)100rectangle→ ⟨Rectangle B⟩ = Rectangle(4, 6)101triangle = Triangle(3, 4, 5)self.a ← 3, self.b ← 4, self.c ← 5
66def __init__(self⟨Triangle C⟩, a3, b4, c5):67 self.a→ 3 = a368 self.b→ 4 = b469 self.c→ 5 = c5triangle ← ⟨Triangle C⟩, shapes ← [⟨Circle A⟩, ⟨Rectangle B⟩]
100rectangle = Rectangle(4, 6)101triangle→ ⟨Triangle C⟩ = Triangle(3, 4, 5)102103shapes→ [⟨Circle A⟩, ⟨Rectangle B⟩] = [circle⟨Circle A⟩, rectangle⟨Rectangle B⟩]for shape in shapes:
pass 1 of 2105# Describe each shape106for shape⟨Circle A⟩ in shapes[⟨Circle A⟩, ⟨Rectangle B⟩]:107 describe_shape(shape⟨Circle A⟩)108 print()def describe_shape(shape: Shape):
pass 1 of 283# Utility function that works with any Shape84def describe_shape(shape⟨Circle A⟩: Shape):85 """Works with any Shape - relies on abstract methods."""86 print(f"Shape: {shape⟨Circle A⟩.name()}")87 print(f" Area: {shape.area():.2f}")def name(self) -> str:
pass 1 of 242def name(self⟨Circle A⟩) -> str:43 return "Circle"print(f"Shape: {shape.name()}")
85"""Works with any Shape - relies on abstract methods."""86print(f"Shape: {shape⟨Circle A⟩.name()}")87print(f" Area: {shape⟨Circle A⟩.area():.2f}")88print(f" Perimeter: {shape.perimeter():.2f}")outputShape: Circledef area(self) -> float:
pass 1 of 236def area(self⟨Circle A⟩) -> float:37 return Circle.PI3.14159 * self.radius5 ** 2print(f" Area: {shape.area():.2f}")
86print(f"Shape: {shape.name()}")87print(f" Area: {shape⟨Circle A⟩.area():.2f}")88print(f" Perimeter: {shape⟨Circle A⟩.perimeter():.2f}")output Area: 78.54def perimeter(self) -> float:
39def perimeter(self⟨Circle A⟩) -> float:40 return 2 * Circle.PI3.14159 * self.radius5print(f" Perimeter: {shape.perimeter():.2f}")
87print(f" Area: {shape.area():.2f}")88print(f" Perimeter: {shape⟨Circle A⟩.perimeter():.2f}")output Perimeter: 31.42describe_shape(shape)
106for shape in shapes:107 describe_shape(shape⟨Circle A⟩)108 print()for shape in shapes:
pass 2 of 2105# Describe each shape106for shape⟨Rectangle B⟩ in shapes[⟨Circle A⟩, ⟨Rectangle B⟩]:107 describe_shape(shape⟨Rectangle B⟩)108 print()def describe_shape(shape: Shape):
pass 2 of 283# Utility function that works with any Shape84def describe_shape(shape⟨Rectangle B⟩: Shape):85 """Works with any Shape - relies on abstract methods."""86 print(f"Shape: {shape⟨Rectangle B⟩.name()}")87 print(f" Area: {shape.area():.2f}")def name(self) -> str:
pass 1 of 259def name(self⟨Rectangle B⟩) -> str:60 return "Rectangle"print(f"Shape: {shape.name()}")
85"""Works with any Shape - relies on abstract methods."""86print(f"Shape: {shape⟨Rectangle B⟩.name()}")87print(f" Area: {shape⟨Rectangle B⟩.area():.2f}")88print(f" Perimeter: {shape.perimeter():.2f}")outputShape: Rectangledef area(self) -> float:
pass 1 of 253def area(self⟨Rectangle B⟩) -> float:54 return self.width4 * self.height6print(f" Area: {shape.area():.2f}")
86print(f"Shape: {shape.name()}")87print(f" Area: {shape⟨Rectangle B⟩.area():.2f}")88print(f" Perimeter: {shape⟨Rectangle B⟩.perimeter():.2f}")output Area: 24.00def perimeter(self) -> float:
56def perimeter(self⟨Rectangle B⟩) -> float:57 return 2 * (self.width4 + self.height6)print(f" Perimeter: {shape.perimeter():.2f}")
87print(f" Area: {shape.area():.2f}")88print(f" Perimeter: {shape⟨Rectangle B⟩.perimeter():.2f}")output Perimeter: 20.00describe_shape(shape)
106for shape in shapes:107 describe_shape(shape⟨Rectangle B⟩)108 print()total = total_area(shapes)
110# Calculate total area111total = total_area(shapes[⟨Circle A⟩, ⟨Rectangle B⟩])112print(f"Total area of all shapes: {total:.2f}")def total_area(shapes: list) -> float:
91def total_area(shapes[⟨Circle A⟩, ⟨Rectangle B⟩]: list) -> float:92 """Calculate total area of all shapes."""93 return sum(shape.area() for shape in shapes[⟨Circle A⟩, ⟨Rectangle B⟩])def area(self) -> float:
pass 2 of 236def area(self⟨Circle A⟩) -> float:37 return Circle.PI3.14159 * self.radius5 ** 2def area(self) -> float:
pass 2 of 253def area(self⟨Rectangle B⟩) -> float:54 return self.width4 * self.height6total ← 102.53975
110# Calculate total area111total→ 102.53975 = total_area(shapes[⟨Circle A⟩, ⟨Rectangle B⟩])112print(f"Total area of all shapes: {total102.53975:.2f}")113114# Abstract method signature documentation115print("\n--- Method Signatures ---")116print("Shape abstract methods define the interface:")117print(f" area() -> float")118print(f" perimeter() -> float")119print(f" name() -> str")120121# Type checking with isinstance122print("\n--- Type Checking ---")123for shape in shapes:outputTotal area of all shapes: 102.54 --- Method Signatures --- Shape abstract methods define the interface: area() -> float perimeter() -> float name() -> str --- Type Checking ---for shape in shapes:
pass 1 of 2122print("\n--- Type Checking ---")123for shape⟨Circle A⟩ in shapes[⟨Circle A⟩, ⟨Rectangle B⟩]:124 print(f"{shape⟨Circle A⟩.name()} is a Shape: {isinstance(shape, Shape<class '__main__.Shape'>)}")def name(self) -> str:
pass 2 of 242def name(self⟨Circle A⟩) -> str:43 return "Circle"print(f"{shape.name()} is a Shape: {isinstance(shape, Shape)}")
123for shape in shapes:124 print(f"{shape⟨Circle A⟩.name()} is a Shape: {isinstance(shape, Shape<class '__main__.Shape'>)}")outputCircle is a Shape: Truefor shape in shapes:
pass 2 of 2122print("\n--- Type Checking ---")123for shape⟨Rectangle B⟩ in shapes[⟨Circle A⟩, ⟨Rectangle B⟩]:124 print(f"{shape⟨Rectangle B⟩.name()} is a Shape: {isinstance(shape, Shape<class '__main__.Shape'>)}")def name(self) -> str:
pass 2 of 259def name(self⟨Rectangle B⟩) -> str:60 return "Rectangle"print(f"{shape.name()} is a Shape: {isinstance(shape, Shape)}")
123for shape in shapes:124 print(f"{shape⟨Rectangle B⟩.name()} is a Shape: {isinstance(shape, Shape<class '__main__.Shape'>)}")outputRectangle is a Shape: Trueprint(" === Why Abstract Methods? ===")
126print("\n=== Why Abstract Methods? ===")127print("""1281. Enforce Contract:129 - Subclasses MUST implement these methods130 - Compile-time-like safety at class definition1311322. Document Interface:133 - Clear what methods are required134 - Type hints show expected return types1351363. Enable Polymorphism:137 - Functions can depend on abstract methods138 - Works with any concrete implementation1391404. Design Patterns:141 - Strategy pattern142 - Template method pattern143 - Factory pattern144""")output === Why Abstract Methods? === 1. Enforce Contract: - Subclasses MUST implement these methods - Compile-time-like safety at class definition 2. Document Interface: - Clear what methods are required - Type hints show expected return types 3. Enable Polymorphism: - Functions can depend on abstract methods - Works with any concrete implementation 4. Design Patterns: - Strategy pattern - Template method pattern - Factory pattern
PI ← (empty)
6class Shape(ABC):7 """8 Abstract shape class.9 Defines what all shapes must do.10 """11 12 @abstractmethod13 def area(self) -> float:14 """Calculate and return the area."""15 pass16 17 @abstractmethod18 def perimeter(self) -> float:19 """Calculate and return the perimeter."""20 pass21 22 @abstractmethod23 def name(self) -> str:24 """Return the name of the shape."""25 pass262728class Circle(Shape):29 """Concrete implementation of Shape for circles."""30 31 PI→ (empty) = 3.1415932 33 def __init__(self, radius):34 self.radius = radius35 36 def area(self) -> float:37 return Circle.PI * self.radius ** 238 39 def perimeter(self) -> float:40 return 2 * Circle.PI * self.radius41 42 def name(self) -> str:43 return "Circle"444546class Rectangle(Shape):47 """Concrete implementation of Shape for rectangles."""48 49 def __init__(self, width, height):50 self.width = width51 self.height = height52 53 def area(self) -> float:54 return self.width * self.height55 56 def perimeter(self) -> float:57 return 2 * (self.width + self.height)58 59 def name(self) -> str:60 return "Rectangle"616263class Triangle(Shape):64 """Concrete implementation for triangles."""65 66 def __init__(self, a, b, c):67 self.a = a68 self.b = b69 self.c = c70 71 def area(self) -> float:72 # Heron's formula73 s = (self.a + self.b + self.c) / 274 return (s * (s - self.a) * (s - self.b) * (s - self.c)) ** 0.575 76 def perimeter(self) -> float:77 return self.a + self.b + self.c78 79 def name(self) -> str:80 return "Triangle"818283# Utility function that works with any Shape84def describe_shape(shape: Shape):85 """Works with any Shape - relies on abstract methods."""86 print(f"Shape: {shape.name()}")87 print(f" Area: {shape.area():.2f}")88 print(f" Perimeter: {shape.perimeter():.2f}")899091def total_area(shapes: list) -> float:92 """Calculate total area of all shapes."""93 return sum(shape.area() for shape in shapes)949596print("=== Abstract Methods ===\n")9798# Create shapes99circle = Circle(5)100rectangle = Rectangle(4, 6)output=== Abstract Methods ===self.radius ← 5
33def __init__(self⟨Circle A⟩, radius5):34 self.radius→ 5 = radius5circle ← ⟨Circle A⟩
98# Create shapes99circle→ ⟨Circle A⟩ = Circle(5)100rectangle = Rectangle(4, 6)101triangle = Triangle(3, 4, 5)self.width ← 4, self.height ← 6
49def __init__(self⟨Rectangle B⟩, width4, height6):50 self.width→ 4 = width451 self.height→ 6 = height6rectangle ← ⟨Rectangle B⟩
99circle = Circle(5)100rectangle→ ⟨Rectangle B⟩ = Rectangle(4, 6)101triangle = Triangle(3, 4, 5)self.a ← 3, self.b ← 4, self.c ← 5
66def __init__(self⟨Triangle C⟩, a3, b4, c5):67 self.a→ 3 = a368 self.b→ 4 = b469 self.c→ 5 = c5triangle ← ⟨Triangle C⟩, shapes ← [⟨Triangle C⟩]
100rectangle = Rectangle(4, 6)101triangle→ ⟨Triangle C⟩ = Triangle(3, 4, 5)102103shapes→ [⟨Triangle C⟩] = [triangle⟨Triangle C⟩]for shape in shapes:
105# Describe each shape106for shape⟨Triangle C⟩ in shapes[⟨Triangle C⟩]:107 describe_shape(shape⟨Triangle C⟩)108 print()def describe_shape(shape: Shape):
83# Utility function that works with any Shape84def describe_shape(shape⟨Triangle C⟩: Shape):85 """Works with any Shape - relies on abstract methods."""86 print(f"Shape: {shape⟨Triangle C⟩.name()}")87 print(f" Area: {shape.area():.2f}")def name(self) -> str:
pass 1 of 279def name(self⟨Triangle C⟩) -> str:80 return "Triangle"print(f"Shape: {shape.name()}")
85"""Works with any Shape - relies on abstract methods."""86print(f"Shape: {shape⟨Triangle C⟩.name()}")87print(f" Area: {shape⟨Triangle C⟩.area():.2f}")88print(f" Perimeter: {shape.perimeter():.2f}")outputShape: Triangles ← 6.0
pass 1 of 271def area(self⟨Triangle C⟩) -> float:72 # Heron's formula73 s→ 6.0 = (self.a3 + self.b4 + self.c5) / 274 return (s6.0 * (s - self.a3) * (s - self.b4) * (s - self.c5)) ** 0.5print(f" Area: {shape.area():.2f}")
86print(f"Shape: {shape.name()}")87print(f" Area: {shape⟨Triangle C⟩.area():.2f}")88print(f" Perimeter: {shape⟨Triangle C⟩.perimeter():.2f}")output Area: 6.00def perimeter(self) -> float:
76def perimeter(self⟨Triangle C⟩) -> float:77 return self.a3 + self.b4 + self.c5print(f" Perimeter: {shape.perimeter():.2f}")
87print(f" Area: {shape.area():.2f}")88print(f" Perimeter: {shape⟨Triangle C⟩.perimeter():.2f}")output Perimeter: 12.00describe_shape(shape)
106for shape in shapes:107 describe_shape(shape⟨Triangle C⟩)108 print()total = total_area(shapes)
110# Calculate total area111total = total_area(shapes[⟨Triangle C⟩])112print(f"Total area of all shapes: {total:.2f}")def total_area(shapes: list) -> float:
91def total_area(shapes[⟨Triangle C⟩]: list) -> float:92 """Calculate total area of all shapes."""93 return sum(shape.area() for shape in shapes[⟨Triangle C⟩])s ← 6.0
pass 2 of 271def area(self⟨Triangle C⟩) -> float:72 # Heron's formula73 s→ 6.0 = (self.a3 + self.b4 + self.c5) / 274 return (s6.0 * (s - self.a3) * (s - self.b4) * (s - self.c5)) ** 0.5total ← 6.0
110# Calculate total area111total→ 6.0 = total_area(shapes[⟨Triangle C⟩])112print(f"Total area of all shapes: {total6.0:.2f}")113114# Abstract method signature documentation115print("\n--- Method Signatures ---")116print("Shape abstract methods define the interface:")117print(f" area() -> float")118print(f" perimeter() -> float")119print(f" name() -> str")120121# Type checking with isinstance122print("\n--- Type Checking ---")123for shape in shapes:outputTotal area of all shapes: 6.00 --- Method Signatures --- Shape abstract methods define the interface: area() -> float perimeter() -> float name() -> str --- Type Checking ---for shape in shapes:
122print("\n--- Type Checking ---")123for shape⟨Triangle C⟩ in shapes[⟨Triangle C⟩]:124 print(f"{shape⟨Triangle C⟩.name()} is a Shape: {isinstance(shape, Shape<class '__main__.Shape'>)}")def name(self) -> str:
pass 2 of 279def name(self⟨Triangle C⟩) -> str:80 return "Triangle"print(f"{shape.name()} is a Shape: {isinstance(shape, Shape)}")
123for shape in shapes:124 print(f"{shape⟨Triangle C⟩.name()} is a Shape: {isinstance(shape, Shape<class '__main__.Shape'>)}")outputTriangle is a Shape: Trueprint(" === Why Abstract Methods? ===")
126print("\n=== Why Abstract Methods? ===")127print("""1281. Enforce Contract:129 - Subclasses MUST implement these methods130 - Compile-time-like safety at class definition1311322. Document Interface:133 - Clear what methods are required134 - Type hints show expected return types1351363. Enable Polymorphism:137 - Functions can depend on abstract methods138 - Works with any concrete implementation1391404. Design Patterns:141 - Strategy pattern142 - Template method pattern143 - Factory pattern144""")output === Why Abstract Methods? === 1. Enforce Contract: - Subclasses MUST implement these methods - Compile-time-like safety at class definition 2. Document Interface: - Clear what methods are required - Type hints show expected return types 3. Enable Polymorphism: - Functions can depend on abstract methods - Works with any concrete implementation 4. Design Patterns: - Strategy pattern - Template method pattern - Factory pattern
@abstractmethod decorator. Subclass must override or it's also abstract.
Concrete methods in ABC
Mix abstract and regular methods.
# Mixing Abstract and Concrete Methods
from abc import ABC, abstractmethod
class DataProcessor(ABC):
"""
Abstract class with both abstract and concrete methods.
Concrete methods provide reusable functionality.
"""
def __init__(self, name):
self.name = name
self.processed_count = 0
# Abstract methods - MUST be implemented
@abstractmethod
def validate(self, data) -> bool:
"""Validate the data - implementation varies by type."""
pass
@abstractmethod
def transform(self, data):
"""Transform the data - implementation varies by type."""
pass
@abstractmethod
def save(self, data):
"""Save the transformed data - implementation varies."""
pass
# Concrete methods - inherited by all subclasses
def process(self, data):
"""
Template method pattern - concrete method using abstract methods.
This orchestrates the processing workflow.
"""
print(f"[{self.name}] Starting processing...")
# Step 1: Validate
if not self.validate(data):
print(f"[{self.name}] Validation failed!")
return None
print(f"[{self.name}] Validation passed")
# Step 2: Transform
result = self.transform(data)
print(f"[{self.name}] Transformation complete")
# Step 3: Save
self.save(result)
print(f"[{self.name}] Data saved")
# Step 4: Update stats (concrete)
self.processed_count += 1
return result
def get_stats(self):
"""Concrete method - shared by all processors."""
return f"{self.name}: processed {self.processed_count} items"
class JSONProcessor(DataProcessor):
"""Processes JSON-like data (dictionaries)."""
def __init__(self):
super().__init__("JSONProcessor")
self.storage = []
def validate(self, data) -> bool:
"""JSON validation - must be dict with 'id' field."""
return isinstance(data, dict) and 'id' in data
def transform(self, data):
"""Add timestamp to JSON data."""
from datetime import datetime
transformed = data.copy()
transformed['processed_at'] = datetime(2025, 1, 15, 10, 30).isoformat()
return transformed
def save(self, data):
"""Save to internal storage."""
self.storage.append(data)
class CSVProcessor(DataProcessor):
"""Processes CSV-like data (lists of values)."""
def __init__(self):
super().__init__("CSVProcessor")
self.storage = []
def validate(self, data) -> bool:
"""CSV validation - must be list with at least 2 items."""
return isinstance(data, list) and len(data) >= 2
def transform(self, data):
"""Convert all values to strings and strip whitespace."""
return [str(item).strip() for item in data]
def save(self, data):
"""Save to storage as comma-separated string."""
self.storage.append(','.join(data))
class TextProcessor(DataProcessor):
"""Processes plain text data."""
def __init__(self):
super().__init__("TextProcessor")
self.storage = []
def validate(self, data) -> bool:
"""Text validation - must be non-empty string."""
return isinstance(data, str) and len(data.strip()) > 0
def transform(self, data):
"""Normalize text - strip and uppercase."""
return data.strip().upper()
def save(self, data):
"""Save to storage."""
self.storage.append(data)
print("=== Abstract and Concrete Methods ===\n")
# Create processors
json_proc = JSONProcessor()
csv_proc = CSVProcessor()
text_proc = TextProcessor()
# Process JSON data
print("--- JSON Processing ---")
json_data = {"id": 1, "name": "Alice", "email": "alice@example.com"}
result = json_proc.process(json_data)
print(f"Result: {result}")
print()
# Process CSV data
print("--- CSV Processing ---")
csv_data = [" John ", " Doe ", " john@example.com "]
result = csv_proc.process(csv_data)
print(f"Result: {result}")
print()
# Process Text data
print("--- Text Processing ---")
text_data = " hello world "
result = text_proc.process(text_data)
print(f"Result: '{result}'")
# Test validation failure
print("\n--- Validation Failure ---")
bad_json = {"name": "No ID field"}
result = json_proc.process(bad_json)
print(f"Result: {result}")
# Stats using concrete method
print("\n--- Stats (Concrete Method) ---")
print(json_proc.get_stats())
print(csv_proc.get_stats())
print(text_proc.get_stats())
print("\n=== Template Method Pattern ===")
print("""
The process() method is a "Template Method":
1. It's concrete - defined in abstract class
2. It calls abstract methods (validate, transform, save)
3. It defines the workflow/algorithm skeleton
4. Subclasses customize by implementing abstract methods
Benefits:
- Code reuse (workflow logic is shared)
- Consistent behavior (all processors follow same steps)
- Easy to extend (just implement 3 methods)
- DRY principle (Don't Repeat Yourself)
""")
Abstract class with both abstract and concrete methods.
5class DataProcessor(ABC): #?dataprocessorclass6 """7 Abstract class with both abstract and concrete methods.8 Concrete methods provide reusable functionality.9 """10 11 def __init__(self, name): #?processorinit12 self.name = name13 self.processed_count = 0 #?processedcount14 15 # Abstract methods - MUST be implemented #?abstractsection16 @abstractmethod17 def validate(self, data) -> bool: #?abstractvalidate18 """Validate the data - implementation varies by type."""19 pass20 21 @abstractmethod22 def transform(self, data): #?abstracttransform23 """Transform the data - implementation varies by type."""24 pass25 26 @abstractmethod27 def save(self, data): #?abstractsave28 """Save the transformed data - implementation varies."""29 pass30 31 # Concrete methods - inherited by all subclasses #?concretesection32 def process(self, data): #?processmethod33 """34 Template method pattern - concrete method using abstract methods.35 This orchestrates the processing workflow.36 """37 print(f"[{self.name}] Starting processing...") #?processstep138 39 # Step 1: Validate #?validatestep40 if not self.validate(data): #?callvalidate41 print(f"[{self.name}] Validation failed!")42 return None43 print(f"[{self.name}] Validation passed")44 45 # Step 2: Transform #?transformstep46 result = self.transform(data) #?calltransform47 print(f"[{self.name}] Transformation complete")48 49 # Step 3: Save #?savestep50 self.save(result) #?callsave51 print(f"[{self.name}] Data saved")52 53 # Step 4: Update stats (concrete) #?updatestats54 self.processed_count += 155 56 return result57 58 def get_stats(self): #?getstats59 """Concrete method - shared by all processors."""60 return f"{self.name}: processed {self.processed_count} items"616263class JSONProcessor(DataProcessor): #?jsonprocessorclass64 """Processes JSON-like data (dictionaries)."""65 66 def __init__(self): #?jsoninit67 super().__init__("JSONProcessor") #?jsoncallsuper68 self.storage = [] #?jsonstorage69 70 def validate(self, data) -> bool: #?jsonvalidate71 """JSON validation - must be dict with 'id' field."""72 return isinstance(data, dict) and 'id' in data #?jsonvalidatelogic73 74 def transform(self, data): #?jsontransform75 """Add timestamp to JSON data."""76 from datetime import datetime77 transformed = data.copy() #?copyjson78 transformed['processed_at'] = datetime(2025, 1, 15, 10, 30).isoformat() #?addtimestamp79 return transformed80 81 def save(self, data): #?jsonsave82 """Save to internal storage."""83 self.storage.append(data) #?appendstorage848586class CSVProcessor(DataProcessor): #?csvprocessorclass87 """Processes CSV-like data (lists of values)."""88 89 def __init__(self): #?csvinit90 super().__init__("CSVProcessor")91 self.storage = []92 93 def validate(self, data) -> bool: #?csvvalidate94 """CSV validation - must be list with at least 2 items."""95 return isinstance(data, list) and len(data) >= 2 #?csvvalidatelogic96 97 def transform(self, data): #?csvtransform98 """Convert all values to strings and strip whitespace."""99 return [str(item).strip() for item in data] #?csvtransformlogic100 101 def save(self, data): #?csvsave102 """Save to storage as comma-separated string."""103 self.storage.append(','.join(data)) #?csvjoin104105106class TextProcessor(DataProcessor): #?textprocessorclass107 """Processes plain text data."""108 109 def __init__(self): #?textinit110 super().__init__("TextProcessor")111 self.storage = []112 113 def validate(self, data) -> bool: #?textvalidate114 """Text validation - must be non-empty string."""115 return isinstance(data, str) and len(data.strip()) > 0116 117 def transform(self, data): #?texttransform118 """Normalize text - strip and uppercase."""119 return data.strip().upper() #?texttransformlogic120 121 def save(self, data): #?textsave122 """Save to storage."""123 self.storage.append(data)124125126print("=== Abstract and Concrete Methods ===\n")127128# Create processors #?createprocessors129json_proc = JSONProcessor() #?createjson130csv_proc = CSVProcessor() #?createcsvoutput=== Abstract and Concrete Methods ===def __init__(self): #?jsoninit
66def __init__(self⟨JSONProcessor A⟩): #?jsoninit67 super().__init__("JSONProcessor") #?jsoncallsuper68 self.storage = [] #?jsonstorageself.name ← JSONProcessor, self.processed_count ← 0
pass 1 of 311def __init__(self⟨JSONProcessor A⟩, nameJSONProcessor): #?processorinit12 self.name→ JSONProcessor = nameJSONProcessor13 self.processed_count→ 0 = 0 #?processedcountAll 3 passes — pass 1 is the card above pass selfnameself.nameself.processed_count1 ⟨JSONProcessor A⟩ JSONProcessor JSONProcessor 0 2 ⟨CSVProcessor B⟩ CSVProcessor CSVProcessor 0 3 ⟨TextProcessor C⟩ TextProcessor TextProcessor 0 self.storage ← []
67super().__init__("JSONProcessor") #?jsoncallsuper68self.storage→ [] = [] #?jsonstoragejson_proc ← ⟨JSONProcessor A⟩
128# Create processors #?createprocessors129json_proc→ ⟨JSONProcessor A⟩ = JSONProcessor() #?createjson130csv_proc = CSVProcessor() #?createcsv131text_proc = TextProcessor() #?createtextdef __init__(self): #?csvinit
89def __init__(self⟨CSVProcessor B⟩): #?csvinit90 super().__init__("CSVProcessor")91 self.storage = []self.storage ← []
90super().__init__("CSVProcessor")91self.storage→ [] = []csv_proc ← ⟨CSVProcessor B⟩
129json_proc = JSONProcessor() #?createjson130csv_proc→ ⟨CSVProcessor B⟩ = CSVProcessor() #?createcsv131text_proc = TextProcessor() #?createtextdef __init__(self): #?textinit
109def __init__(self⟨TextProcessor C⟩): #?textinit110 super().__init__("TextProcessor")111 self.storage = []self.storage ← []
110super().__init__("TextProcessor")111self.storage→ [] = []text_proc ← ⟨TextProcessor C⟩, json_data ← {'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}
130csv_proc = CSVProcessor() #?createcsv131text_proc→ ⟨TextProcessor C⟩ = TextProcessor() #?createtext132133# Process JSON data #?processjsondemo134print("--- JSON Processing ---")135json_data→ {'id': 1, 'name': 'Alice', 'email': 'alice@example.com'} = {"id": 1, "name": "Alice", "email": "alice@example.com"} #?jsondata136result = json_proc⟨JSONProcessor A⟩.process(json_data{'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}) #?processjson137print(f"Result: {result}")output--- JSON Processing ---def process(self, data): #?processmethod
pass 1 of 431# Concrete methods - inherited by all subclasses #?concretesection32def process(self⟨JSONProcessor A⟩, data{'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}): #?processmethod33 """34 Template method pattern - concrete method using abstract methods.35 This orchestrates the processing workflow.36 """37 print(f"[{self.nameJSONProcessor}] Starting processing...") #?processstep1output[JSONProcessor] Starting processing...All 4 passes — pass 1 is the card above pass selfdataself.name1 ⟨JSONProcessor A⟩ {'id': 1, 'name': 'Alice', 'email': 'alice@example.com'} JSONProcessor 2 ⟨CSVProcessor B⟩ [' John ', ' Doe ', ' john@example.com '] CSVProcessor 3 ⟨TextProcessor C⟩ hello world TextProcessor 4 ⟨JSONProcessor A⟩ {'name': 'No ID field'} JSONProcessor def validate(self, data) -> bool: #?jsonvalidate
pass 1 of 270def validate(self⟨JSONProcessor A⟩, data{'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}) -> bool: #?jsonvalidate71 """JSON validation - must be dict with 'id' field."""72 return isinstance(data{'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}, dict) and 'id' in data #?jsonvalidatelogicprint(f"[{self.name}] Validation passed")
42 return None43print(f"[{self.nameJSONProcessor}] Validation passed")4445# Step 2: Transform #?transformstep46result = self.transform(data{'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}) #?calltransform47print(f"[{self.name}] Transformation complete")output[JSONProcessor] Validation passedtransformed ← {'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}
74def transform(self⟨JSONProcessor A⟩, data{'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}): #?jsontransform75 """Add timestamp to JSON data."""76 from datetime import datetime77 transformed→ {'id': 1, 'name': 'Alice', 'email': 'alice@example.com'} = data{'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}.copy() #?copyjson78 transformed['processed_at']→ 2025-01-15T10:30:00 = datetime(2025, 1, 15, 10, 30).isoformat() #?addtimestamp79 return transformed{'id': 1, 'name': 'Alice', 'email': 'alice@example.com', 'processed_at': '2025-01-15T10:30:00'}result ← {'id': 1, 'name': 'Alice', 'email': 'alice@example.com', 'processed_at': '2025-01-15T10:30:00'}
45# Step 2: Transform #?transformstep46result→ {'id': 1, 'name': 'Alice', 'email': 'alice@example.com', 'processed_at': '2025-01-15T10:30:00'} = self.transform(data{'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}) #?calltransform47print(f"[{self.nameJSONProcessor}] Transformation complete")4849# Step 3: Save #?savestep50self.save(result{'id': 1, 'name': 'Alice', 'email': 'alice@example.com', 'processed_at': '2025-01-15T10:30:00'}) #?callsave51print(f"[{self.name}] Data saved")output[JSONProcessor] Transformation completeself.storage ← [{'id': 1, 'name': 'Alice', 'email': 'alice@example.com', 'processed_at': '2025-01-15T10:30:00'}]
49 # Step 3: Save #?savestep50 self.save(result{'id': 1, 'name': 'Alice', 'email': 'alice@example.com', 'processed_at': '2025-01-15T10:30:00'}) #?callsave51 print(f"[{self.nameJSONProcessor}] Data saved")52 53 # Step 4: Update stats (concrete) #?updatestats54 self.processed_count→ 1 += 155 56 return result{'id': 1, 'name': 'Alice', 'email': 'alice@example.com', 'processed_at': '2025-01-15T10:30:00'}57 58 def get_stats(self): #?getstats59 """Concrete method - shared by all processors."""60 return f"{self.name}: processed {self.processed_count} items"616263class JSONProcessor(DataProcessor): #?jsonprocessorclass64 """Processes JSON-like data (dictionaries)."""65 66 def __init__(self): #?jsoninit67 super().__init__("JSONProcessor") #?jsoncallsuper68 self.storage = [] #?jsonstorage69 70 def validate(self, data) -> bool: #?jsonvalidate71 """JSON validation - must be dict with 'id' field."""72 return isinstance(data, dict) and 'id' in data #?jsonvalidatelogic73 74 def transform(self, data): #?jsontransform75 """Add timestamp to JSON data."""76 from datetime import datetime77 transformed = data.copy() #?copyjson78 transformed['processed_at'] = datetime(2025, 1, 15, 10, 30).isoformat() #?addtimestamp79 return transformed80 81 def save(self⟨JSONProcessor A⟩, data{'id': 1, 'name': 'Alice', 'email': 'alice@example.com', 'processed_at': '2025-01-15T10:30:00'}): #?jsonsave82 """Save to internal storage."""83 self.storage→ [{'id': 1, 'name': 'Alice', 'email': 'alice@example.com', 'processed_at': '2025-01-15T10:30:00'}].append(data{'id': 1, 'name': 'Alice', 'email': 'alice@example.com', 'processed_at': '2025-01-15T10:30:00'}) #?appendstorageoutput[JSONProcessor] Data savedresult ← {'id': 1, 'name': 'Alice', 'email': 'alice@example.com', 'processed_at': '2025-01-15T10:30:00'}
135json_data = {"id": 1, "name": "Alice", "email": "alice@example.com"} #?jsondata136result→ {'id': 1, 'name': 'Alice', 'email': 'alice@example.com', 'processed_at': '2025-01-15T10:30:00'} = json_proc⟨JSONProcessor A⟩.process(json_data{'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}) #?processjson137print(f"Result: {result{'id': 1, 'name': 'Alice', 'email': 'alice@example.com', 'processed_at': '2025-01-15T10:30:00'}}")138139print()140141# Process CSV data #?processcsvdemo142print("--- CSV Processing ---")143csv_data→ [' John ', ' Doe ', ' john@example.com '] = [" John ", " Doe ", " john@example.com "] #?csvdata144result = csv_proc⟨CSVProcessor B⟩.process(csv_data[' John ', ' Doe ', ' john@example.com ']) #?processcsv145print(f"Result: {result}")outputResult: {'id': 1, 'name': 'Alice', 'email': 'alice@example.com', 'processed_at': '2025-01-15T10:30:00'} --- CSV Processing ---def validate(self, data) -> bool: #?csvvalidate
93def validate(self⟨CSVProcessor B⟩, data[' John ', ' Doe ', ' john@example.com ']) -> bool: #?csvvalidate94 """CSV validation - must be list with at least 2 items."""95 return isinstance(data[' John ', ' Doe ', ' john@example.com '], list) and len(data) >= 2 #?csvvalidatelogicprint(f"[{self.name}] Validation passed")
42 return None43print(f"[{self.nameCSVProcessor}] Validation passed")4445# Step 2: Transform #?transformstep46result = self.transform(data[' John ', ' Doe ', ' john@example.com ']) #?calltransform47print(f"[{self.name}] Transformation complete")output[CSVProcessor] Validation passeddef transform(self, data): #?csvtransform
97def transform(self⟨CSVProcessor B⟩, data[' John ', ' Doe ', ' john@example.com ']): #?csvtransform98 """Convert all values to strings and strip whitespace."""99 return [str(item).strip() for item in data[' John ', ' Doe ', ' john@example.com ']] #?csvtransformlogicresult ← ['John', 'Doe', 'john@example.com']
45# Step 2: Transform #?transformstep46result→ ['John', 'Doe', 'john@example.com'] = self.transform(data[' John ', ' Doe ', ' john@example.com ']) #?calltransform47print(f"[{self.nameCSVProcessor}] Transformation complete")4849# Step 3: Save #?savestep50self.save(result['John', 'Doe', 'john@example.com']) #?callsave51print(f"[{self.name}] Data saved")output[CSVProcessor] Transformation completeself.storage ← ['John,Doe,john@example.com'], self.processed_count ← 1
49 # Step 3: Save #?savestep50 self.save(result['John', 'Doe', 'john@example.com']) #?callsave51 print(f"[{self.nameCSVProcessor}] Data saved")52 53 # Step 4: Update stats (concrete) #?updatestats54 self.processed_count→ 1 += 155 56 return result['John', 'Doe', 'john@example.com']57 58 def get_stats(self): #?getstats59 """Concrete method - shared by all processors."""60 return f"{self.name}: processed {self.processed_count} items"616263class JSONProcessor(DataProcessor): #?jsonprocessorclass64 """Processes JSON-like data (dictionaries)."""65 66 def __init__(self): #?jsoninit67 super().__init__("JSONProcessor") #?jsoncallsuper68 self.storage = [] #?jsonstorage69 70 def validate(self, data) -> bool: #?jsonvalidate71 """JSON validation - must be dict with 'id' field."""72 return isinstance(data, dict) and 'id' in data #?jsonvalidatelogic73 74 def transform(self, data): #?jsontransform75 """Add timestamp to JSON data."""76 from datetime import datetime77 transformed = data.copy() #?copyjson78 transformed['processed_at'] = datetime(2025, 1, 15, 10, 30).isoformat() #?addtimestamp79 return transformed80 81 def save(self, data): #?jsonsave82 """Save to internal storage."""83 self.storage.append(data) #?appendstorage848586class CSVProcessor(DataProcessor): #?csvprocessorclass87 """Processes CSV-like data (lists of values)."""88 89 def __init__(self): #?csvinit90 super().__init__("CSVProcessor")91 self.storage = []92 93 def validate(self, data) -> bool: #?csvvalidate94 """CSV validation - must be list with at least 2 items."""95 return isinstance(data, list) and len(data) >= 2 #?csvvalidatelogic96 97 def transform(self, data): #?csvtransform98 """Convert all values to strings and strip whitespace."""99 return [str(item).strip() for item in data] #?csvtransformlogic100 101 def save(self⟨CSVProcessor B⟩, data['John', 'Doe', 'john@example.com']): #?csvsave102 """Save to storage as comma-separated string."""103 self.storage→ ['John,Doe,john@example.com'].append(','.join(data['John', 'Doe', 'john@example.com'])) #?csvjoinoutput[CSVProcessor] Data savedresult ← ['John', 'Doe', 'john@example.com'], text_data ← hello world
143csv_data = [" John ", " Doe ", " john@example.com "] #?csvdata144result→ ['John', 'Doe', 'john@example.com'] = csv_proc⟨CSVProcessor B⟩.process(csv_data[' John ', ' Doe ', ' john@example.com ']) #?processcsv145print(f"Result: {result['John', 'Doe', 'john@example.com']}")146147print()148149# Process Text data #?processtextdemo150print("--- Text Processing ---")151text_data→ hello world = " hello world " #?textdata152result = text_proc⟨TextProcessor C⟩.process(text_data hello world ) #?processtext153print(f"Result: '{result}'")outputResult: ['John', 'Doe', 'john@example.com'] --- Text Processing ---def validate(self, data) -> bool: #?textvalidate
113def validate(self⟨TextProcessor C⟩, data hello world ) -> bool: #?textvalidate114 """Text validation - must be non-empty string."""115 return isinstance(data hello world , str) and len(data.strip()) > 0print(f"[{self.name}] Validation passed")
42 return None43print(f"[{self.nameTextProcessor}] Validation passed")4445# Step 2: Transform #?transformstep46result = self.transform(data hello world ) #?calltransform47print(f"[{self.name}] Transformation complete")output[TextProcessor] Validation passeddef transform(self, data): #?texttransform
117def transform(self⟨TextProcessor C⟩, data hello world ): #?texttransform118 """Normalize text - strip and uppercase."""119 return data hello world .strip().upper() #?texttransformlogicresult ← HELLO WORLD
45# Step 2: Transform #?transformstep46result→ HELLO WORLD = self.transform(data hello world ) #?calltransform47print(f"[{self.nameTextProcessor}] Transformation complete")4849# Step 3: Save #?savestep50self.save(resultHELLO WORLD) #?callsave51print(f"[{self.name}] Data saved")output[TextProcessor] Transformation completeself.storage ← ['HELLO WORLD'], self.processed_count ← 1
49 # Step 3: Save #?savestep50 self.save(resultHELLO WORLD) #?callsave51 print(f"[{self.nameTextProcessor}] Data saved")52 53 # Step 4: Update stats (concrete) #?updatestats54 self.processed_count→ 1 += 155 56 return resultHELLO WORLD57 58 def get_stats(self): #?getstats59 """Concrete method - shared by all processors."""60 return f"{self.name}: processed {self.processed_count} items"616263class JSONProcessor(DataProcessor): #?jsonprocessorclass64 """Processes JSON-like data (dictionaries)."""65 66 def __init__(self): #?jsoninit67 super().__init__("JSONProcessor") #?jsoncallsuper68 self.storage = [] #?jsonstorage69 70 def validate(self, data) -> bool: #?jsonvalidate71 """JSON validation - must be dict with 'id' field."""72 return isinstance(data, dict) and 'id' in data #?jsonvalidatelogic73 74 def transform(self, data): #?jsontransform75 """Add timestamp to JSON data."""76 from datetime import datetime77 transformed = data.copy() #?copyjson78 transformed['processed_at'] = datetime(2025, 1, 15, 10, 30).isoformat() #?addtimestamp79 return transformed80 81 def save(self, data): #?jsonsave82 """Save to internal storage."""83 self.storage.append(data) #?appendstorage848586class CSVProcessor(DataProcessor): #?csvprocessorclass87 """Processes CSV-like data (lists of values)."""88 89 def __init__(self): #?csvinit90 super().__init__("CSVProcessor")91 self.storage = []92 93 def validate(self, data) -> bool: #?csvvalidate94 """CSV validation - must be list with at least 2 items."""95 return isinstance(data, list) and len(data) >= 2 #?csvvalidatelogic96 97 def transform(self, data): #?csvtransform98 """Convert all values to strings and strip whitespace."""99 return [str(item).strip() for item in data] #?csvtransformlogic100 101 def save(self, data): #?csvsave102 """Save to storage as comma-separated string."""103 self.storage.append(','.join(data)) #?csvjoin104105106class TextProcessor(DataProcessor): #?textprocessorclass107 """Processes plain text data."""108 109 def __init__(self): #?textinit110 super().__init__("TextProcessor")111 self.storage = []112 113 def validate(self, data) -> bool: #?textvalidate114 """Text validation - must be non-empty string."""115 return isinstance(data, str) and len(data.strip()) > 0116 117 def transform(self, data): #?texttransform118 """Normalize text - strip and uppercase."""119 return data.strip().upper() #?texttransformlogic120 121 def save(self⟨TextProcessor C⟩, dataHELLO WORLD): #?textsave122 """Save to storage."""123 self.storage→ ['HELLO WORLD'].append(dataHELLO WORLD)output[TextProcessor] Data savedresult ← HELLO WORLD, bad_json ← {'name': 'No ID field'}
151text_data = " hello world " #?textdata152result→ HELLO WORLD = text_proc⟨TextProcessor C⟩.process(text_data hello world ) #?processtext153print(f"Result: '{resultHELLO WORLD}'")154155# Test validation failure #?testfailure156print("\n--- Validation Failure ---")157bad_json→ {'name': 'No ID field'} = {"name": "No ID field"} #?badjson158result = json_proc⟨JSONProcessor A⟩.process(bad_json{'name': 'No ID field'}) #?processbad159print(f"Result: {result}")outputResult: 'HELLO WORLD' --- Validation Failure ---def validate(self, data) -> bool: #?jsonvalidate
pass 2 of 270def validate(self⟨JSONProcessor A⟩, data{'name': 'No ID field'}) -> bool: #?jsonvalidate71 """JSON validation - must be dict with 'id' field."""72 return isinstance(data{'name': 'No ID field'}, dict) and 'id' in data #?jsonvalidatelogicif not self.validate(data): #?callvalidate
39# Step 1: Validate #?validatestep40if not self.validate(data{'name': 'No ID field'}): #?callvalidate41 print(f"[{self.nameJSONProcessor}] Validation failed!")42 return None43print(f"[{self.name}] Validation passed")output[JSONProcessor] Validation failed!result ← None
157bad_json = {"name": "No ID field"} #?badjson158result→ None = json_proc⟨JSONProcessor A⟩.process(bad_json{'name': 'No ID field'}) #?processbad159print(f"Result: {resultNone}")160161# Stats using concrete method #?statsdemo162print("\n--- Stats (Concrete Method) ---")163print(json_proc⟨JSONProcessor A⟩.get_stats()) #?jsonstats164print(csv_proc.get_stats()) #?csvstatsoutputResult: None --- Stats (Concrete Method) ---def get_stats(self): #?getstats
pass 1 of 358def get_stats(self⟨JSONProcessor A⟩): #?getstats59 """Concrete method - shared by all processors."""60 return f"{self.nameJSONProcessor}: processed {self.processed_count1} items"All 3 passes — pass 1 is the card above pass selfself.name1 ⟨JSONProcessor A⟩ JSONProcessor 2 ⟨CSVProcessor B⟩ CSVProcessor 3 ⟨TextProcessor C⟩ TextProcessor print(json_proc.get_stats()) #?jsonstats
162print("\n--- Stats (Concrete Method) ---")163print(json_proc⟨JSONProcessor A⟩.get_stats()) #?jsonstats164print(csv_proc⟨CSVProcessor B⟩.get_stats()) #?csvstats165print(text_proc.get_stats()) #?textstatsoutputJSONProcessor: processed 1 itemsprint(csv_proc.get_stats()) #?csvstats
163print(json_proc.get_stats()) #?jsonstats164print(csv_proc⟨CSVProcessor B⟩.get_stats()) #?csvstats165print(text_proc⟨TextProcessor C⟩.get_stats()) #?textstatsoutputCSVProcessor: processed 1 itemsprint(text_proc.get_stats()) #?textstats
164print(csv_proc.get_stats()) #?csvstats165print(text_proc⟨TextProcessor C⟩.get_stats()) #?textstats166167print("\n=== Template Method Pattern ===")168print("""169The process() method is a "Template Method":170 1. It's concrete - defined in abstract class171 2. It calls abstract methods (validate, transform, save)172 3. It defines the workflow/algorithm skeleton173 4. Subclasses customize by implementing abstract methods174175Benefits:176 - Code reuse (workflow logic is shared)177 - Consistent behavior (all processors follow same steps)178 - Easy to extend (just implement 3 methods)179 - DRY principle (Don't Repeat Yourself)180""")outputTextProcessor: processed 1 items === Template Method Pattern === The process() method is a "Template Method": 1. It's concrete - defined in abstract class 2. It calls abstract methods (validate, transform, save) 3. It defines the workflow/algorithm skeleton 4. Subclasses customize by implementing abstract methods Benefits: - Code reuse (workflow logic is shared) - Consistent behavior (all processors follow same steps) - Easy to extend (just implement 3 methods) - DRY principle (Don't Repeat Yourself)
ABC can have implemented methods that subclasses inherit.
Abstract properties
Properties that must be implemented.
# Abstract Properties
from abc import ABC, abstractmethod
class Vehicle(ABC):
"""
Abstract class with abstract properties.
Properties define read-only or read-write attributes.
"""
def __init__(self, brand):
self._brand = brand
# Abstract property - must be implemented
@property
@abstractmethod
def wheels(self) -> int:
"""Number of wheels - varies by vehicle type."""
pass
@property
@abstractmethod
def vehicle_type(self) -> str:
"""Type of vehicle."""
pass
# Concrete property - shared by all
@property
def brand(self) -> str:
"""Brand is concrete - same logic for all."""
return self._brand
# Concrete method using abstract properties
def describe(self) -> str:
return f"{self.brand} {self.vehicle_type} with {self.wheels} wheels"
class Car(Vehicle):
"""Car implementation."""
@property
def wheels(self) -> int:
return 4
@property
def vehicle_type(self) -> str:
return "Car"
class Motorcycle(Vehicle):
"""Motorcycle implementation."""
@property
def wheels(self) -> int:
return 2
@property
def vehicle_type(self) -> str:
return "Motorcycle"
class Truck(Vehicle):
"""Truck implementation with variable wheels."""
def __init__(self, brand, axles):
super().__init__(brand)
self._axles = axles
@property
def wheels(self) -> int:
return self._axles * 2
@property
def vehicle_type(self) -> str:
return f"Truck ({self._axles}-axle)"
# Abstract class with read-write property
class Account(ABC):
"""
Abstract class with abstract setter.
"""
def __init__(self, owner):
self._owner = owner
self._balance = 0.0
@property
def owner(self) -> str:
return self._owner
# Abstract getter and setter
@property
@abstractmethod
def balance(self) -> float:
"""Get current balance."""
pass
@balance.setter
@abstractmethod
def balance(self, value: float):
"""Set balance - may have validation."""
pass
class SavingsAccount(Account):
"""Savings account with minimum balance."""
MINIMUM_BALANCE = 100.0
@property
def balance(self) -> float:
return self._balance
@balance.setter
def balance(self, value: float):
if value < self.MINIMUM_BALANCE:
raise ValueError(f"Balance cannot be below ${self.MINIMUM_BALANCE}")
self._balance = value
class CheckingAccount(Account):
"""Checking account with overdraft protection."""
OVERDRAFT_LIMIT = -500.0
@property
def balance(self) -> float:
return self._balance
@balance.setter
def balance(self, value: float):
if value < self.OVERDRAFT_LIMIT:
raise ValueError(f"Exceeded overdraft limit of ${-self.OVERDRAFT_LIMIT}")
self._balance = value
print("=== Abstract Properties ===\n")
# Vehicle examples
print("--- Vehicles ---")
car = Car("Toyota")
motorcycle = Motorcycle("Harley")
truck = Truck("Volvo", 3)
vehicles = [car, motorcycle, truck]
for v in vehicles:
print(f"{v.describe()}")
print(f" wheels property: {v.wheels}")
print()
# Account examples
print("--- Accounts ---")
savings = SavingsAccount("Alice")
savings.balance = 500.0
print(f"Savings: {savings.owner}, balance=${savings.balance}")
# Try to go below minimum
try:
savings.balance = 50.0
except ValueError as e:
print(f"Error: {e}")
print()
checking = CheckingAccount("Bob")
checking.balance = 100.0
print(f"Checking: {checking.owner}, balance=${checking.balance}")
# Use overdraft
checking.balance = -300.0
print(f"After overdraft: balance=${checking.balance}")
# Try to exceed overdraft
try:
checking.balance = -600.0
except ValueError as e:
print(f"Error: {e}")
print("\n=== Abstract Property Rules ===")
print("""
Syntax for abstract property:
@property
@abstractmethod
def my_property(self):
pass
Syntax for abstract setter:
@my_property.setter
@abstractmethod
def my_property(self, value):
pass
Key points:
1. @property must come BEFORE @abstractmethod
2. Subclass must implement the property
3. Can have abstract getter only, or both getter and setter
4. Concrete properties work normally
""")
MINIMUM_BALANCE ← (empty), OVERDRAFT_LIMIT ← (empty)
5class Vehicle(ABC): #?vehicleclass6 """7 Abstract class with abstract properties.8 Properties define read-only or read-write attributes.9 """10 11 def __init__(self, brand): #?vehicleinit12 self._brand = brand #?privatebrand13 14 # Abstract property - must be implemented #?abstractproperty15 @property #?propertydecorator16 @abstractmethod #?abstractdecorator17 def wheels(self) -> int: #?wheelsabstract18 """Number of wheels - varies by vehicle type."""19 pass20 21 @property22 @abstractmethod23 def vehicle_type(self) -> str: #?vehicletypeabstract24 """Type of vehicle."""25 pass26 27 # Concrete property - shared by all #?concreteproperty28 @property29 def brand(self) -> str: #?brandproperty30 """Brand is concrete - same logic for all."""31 return self._brand32 33 # Concrete method using abstract properties #?concretemethod34 def describe(self) -> str: #?describemethod35 return f"{self.brand} {self.vehicle_type} with {self.wheels} wheels"363738class Car(Vehicle): #?carclass39 """Car implementation."""40 41 @property42 def wheels(self) -> int: #?carwheels43 return 4 #?return444 45 @property46 def vehicle_type(self) -> str: #?carvehicletype47 return "Car"484950class Motorcycle(Vehicle): #?motorcycleclass51 """Motorcycle implementation."""52 53 @property54 def wheels(self) -> int: #?motorcyclewheels55 return 2 #?return256 57 @property58 def vehicle_type(self) -> str: #?motorcyclevehicletype59 return "Motorcycle"606162class Truck(Vehicle): #?truckclass63 """Truck implementation with variable wheels."""64 65 def __init__(self, brand, axles): #?truckinit66 super().__init__(brand) #?truckcallsuper67 self._axles = axles #?axlesattr68 69 @property70 def wheels(self) -> int: #?truckwheels71 return self._axles * 2 #?calculatewheels72 73 @property74 def vehicle_type(self) -> str: #?truckvehicletype75 return f"Truck ({self._axles}-axle)"767778# Abstract class with read-write property #?readwriteclass79class Account(ABC): #?accountclass80 """81 Abstract class with abstract setter.82 """83 84 def __init__(self, owner): #?accountinit85 self._owner = owner86 self._balance = 0.0 #?balanceattr87 88 @property89 def owner(self) -> str: #?ownerproperty90 return self._owner91 92 # Abstract getter and setter #?abstractgettersetter93 @property94 @abstractmethod95 def balance(self) -> float: #?balancegetter96 """Get current balance."""97 pass98 99 @balance.setter #?balancesetter100 @abstractmethod101 def balance(self, value: float): #?balancesetterabstract102 """Set balance - may have validation."""103 pass104105106class SavingsAccount(Account): #?savingsclass107 """Savings account with minimum balance."""108 109 MINIMUM_BALANCE→ (empty) = 100.0 #?minimumbalance110 111 @property112 def balance(self) -> float: #?savingsgetter113 return self._balance114 115 @balance.setter116 def balance(self, value: float): #?savingssetter117 if value < self.MINIMUM_BALANCE: #?checkminimum118 raise ValueError(f"Balance cannot be below ${self.MINIMUM_BALANCE}")119 self._balance = value #?setbalance120121122class CheckingAccount(Account): #?checkingclass123 """Checking account with overdraft protection."""124 125 OVERDRAFT_LIMIT→ (empty) = -500.0 #?overdraftlimit126 127 @property128 def balance(self) -> float: #?checkinggetter129 return self._balance130 131 @balance.setter132 def balance(self, value: float): #?checkingsetter133 if value < self.OVERDRAFT_LIMIT: #?checkoverdraft134 raise ValueError(f"Exceeded overdraft limit of ${-self.OVERDRAFT_LIMIT}")135 self._balance = value136137138print("=== Abstract Properties ===\n")139140# Vehicle examples #?vehicledemo141print("--- Vehicles ---")142car = Car("Toyota") #?createcar143motorcycle = Motorcycle("Harley") #?createmotorcycleoutput=== Abstract Properties === --- Vehicles ---self._brand ← Toyota
pass 1 of 311def __init__(self⟨Car A⟩, brandToyota): #?vehicleinit12 self._brand→ Toyota = brandToyota #?privatebrandAll 3 passes — pass 1 is the card above pass selfbrandself._brand1 ⟨Car A⟩ Toyota Toyota 2 ⟨Motorcycle B⟩ Harley Harley 3 ⟨Truck C⟩ Volvo Volvo car ← ⟨Car A⟩
141print("--- Vehicles ---")142car→ ⟨Car A⟩ = Car("Toyota") #?createcar143motorcycle = Motorcycle("Harley") #?createmotorcycle144truck = Truck("Volvo", 3) #?createtruckmotorcycle ← ⟨Motorcycle B⟩
142car = Car("Toyota") #?createcar143motorcycle→ ⟨Motorcycle B⟩ = Motorcycle("Harley") #?createmotorcycle144truck = Truck("Volvo", 3) #?createtruckdef __init__(self, brand, axles): #?truckinit
65def __init__(self⟨Truck C⟩, brandVolvo, axles3): #?truckinit66 super().__init__(brand) #?truckcallsuper67 self._axles = axles #?axlesattrself._axles ← 3
66super().__init__(brand) #?truckcallsuper67self._axles→ 3 = axles3 #?axlesattrtruck ← ⟨Truck C⟩, vehicles ← [⟨Car A⟩, ⟨Motorcycle B⟩, ⟨Truck C⟩]
143motorcycle = Motorcycle("Harley") #?createmotorcycle144truck→ ⟨Truck C⟩ = Truck("Volvo", 3) #?createtruck145146vehicles→ [⟨Car A⟩, ⟨Motorcycle B⟩, ⟨Truck C⟩] = [car⟨Car A⟩, motorcycle⟨Motorcycle B⟩, truck⟨Truck C⟩] #?vehicleslistfor v in vehicles: #?iteratevehicles
pass 1 of 3148for v⟨Car A⟩ in vehicles[⟨Car A⟩, ⟨Motorcycle B⟩, ⟨Truck C⟩]: #?iteratevehicles149 print(f"{v⟨Car A⟩.describe()}") #?calldescribe150 print(f" wheels property: {v.wheels}") #?accesswheelsAll 3 passes — pass 1 is the card above pass vselfself._axles1 ⟨Car A⟩ ⟨Car A⟩ — 2 ⟨Motorcycle B⟩ ⟨Motorcycle B⟩ — 3 ⟨Truck C⟩ ⟨Truck C⟩ 3 def describe(self) -> str: #?describemethod
pass 1 of 333# Concrete method using abstract properties #?concretemethod34def describe(self⟨Car A⟩) -> str: #?describemethod35 return f"{self.brandToyota} {self.vehicle_typeCar} with {self.wheels4} wheels"All 3 passes — pass 1 is the card above pass selfself.brandself.vehicle_typeself.wheelsself._axles1 ⟨Car A⟩ Toyota Car 4 — 2 ⟨Motorcycle B⟩ Harley Motorcycle 2 — 3 ⟨Truck C⟩ Volvo Truck (3-axle) 6 3 def brand(self) -> str: #?brandproperty
pass 1 of 328@property29def brand(self⟨Car A⟩) -> str: #?brandproperty30 """Brand is concrete - same logic for all."""31 return self._brandToyotaAll 3 passes — pass 1 is the card above pass selfself._brandself._axles1 ⟨Car A⟩ Toyota — 2 ⟨Motorcycle B⟩ Harley — 3 ⟨Truck C⟩ Volvo 3 def vehicle_type(self) -> str: #?carvehicletype
45@property46def vehicle_type(self⟨Car A⟩) -> str: #?carvehicletype47 return "Car"def wheels(self) -> int: #?carwheels
pass 1 of 241@property42def wheels(self⟨Car A⟩) -> int: #?carwheels43 return 4 #?return4print(f"{v.describe()}") #?calldescribe
148for v in vehicles: #?iteratevehicles149 print(f"{v⟨Car A⟩.describe()}") #?calldescribe150 print(f" wheels property: {v.wheels4}") #?accesswheels151 print()outputToyota Car with 4 wheelsdef wheels(self) -> int: #?carwheels
pass 2 of 241@property42def wheels(self⟨Car A⟩) -> int: #?carwheels43 return 4 #?return4print(f" wheels property: {v.wheels}") #?accesswheels
149print(f"{v.describe()}") #?calldescribe150print(f" wheels property: {v.wheels4}") #?accesswheels151print()output wheels property: 4def vehicle_type(self) -> str: #?motorcyclevehicletype
57@property58def vehicle_type(self⟨Motorcycle B⟩) -> str: #?motorcyclevehicletype59 return "Motorcycle"def wheels(self) -> int: #?motorcyclewheels
pass 1 of 253@property54def wheels(self⟨Motorcycle B⟩) -> int: #?motorcyclewheels55 return 2 #?return2print(f"{v.describe()}") #?calldescribe
148for v in vehicles: #?iteratevehicles149 print(f"{v⟨Motorcycle B⟩.describe()}") #?calldescribe150 print(f" wheels property: {v.wheels2}") #?accesswheels151 print()outputHarley Motorcycle with 2 wheelsdef wheels(self) -> int: #?motorcyclewheels
pass 2 of 253@property54def wheels(self⟨Motorcycle B⟩) -> int: #?motorcyclewheels55 return 2 #?return2print(f" wheels property: {v.wheels}") #?accesswheels
149print(f"{v.describe()}") #?calldescribe150print(f" wheels property: {v.wheels2}") #?accesswheels151print()output wheels property: 2def vehicle_type(self) -> str: #?truckvehicletype
73@property74def vehicle_type(self⟨Truck C⟩) -> str: #?truckvehicletype75 return f"Truck ({self._axles3}-axle)"def wheels(self) -> int: #?truckwheels
pass 1 of 269@property70def wheels(self⟨Truck C⟩) -> int: #?truckwheels71 return self._axles3 * 2 #?calculatewheelsprint(f"{v.describe()}") #?calldescribe
148for v in vehicles: #?iteratevehicles149 print(f"{v⟨Truck C⟩.describe()}") #?calldescribe150 print(f" wheels property: {v.wheels6}") #?accesswheels151 print()outputVolvo Truck (3-axle) with 6 wheelsdef wheels(self) -> int: #?truckwheels
pass 2 of 269@property70def wheels(self⟨Truck C⟩) -> int: #?truckwheels71 return self._axles3 * 2 #?calculatewheelsprint(f" wheels property: {v.wheels}") #?accesswheels
149print(f"{v.describe()}") #?calldescribe150print(f" wheels property: {v.wheels6}") #?accesswheels151print()output wheels property: 6print("--- Accounts ---")
153# Account examples #?accountdemo154print("--- Accounts ---")155156savings = SavingsAccount("Alice") #?createsavings157savings.balance = 500.0 #?setsavingsbalanceoutput--- Accounts ---self._owner ← Alice, self._balance ← 0.0
pass 1 of 284def __init__(self⟨SavingsAccount D⟩, ownerAlice): #?accountinit85 self._owner→ Alice = ownerAlice86 self._balance→ 0.0 = 0.0 #?balanceattrsavings ← ⟨SavingsAccount D⟩
156savings→ ⟨SavingsAccount D⟩ = SavingsAccount("Alice") #?createsavings157savings.balance = 500.0 #?setsavingsbalance158print(f"Savings: {savings.owner}, balance=${savings.balance}")self._balance ← 500.0
pass 1 of 2115@balance.setter116def balance(self⟨SavingsAccount D⟩, value500.0: float): #?savingssetter117 if value < self.MINIMUM_BALANCE: #?checkminimum118 raise ValueError(f"Balance cannot be below ${self.MINIMUM_BALANCE}")119 self._balance→ 500.0 = value500.0 #?setbalancesavings.balance ← 500.0
156savings = SavingsAccount("Alice") #?createsavings157savings.balance→ 500.0 = 500.0 #?setsavingsbalance158print(f"Savings: {savings.ownerAlice}, balance=${savings.balance500.0}")def owner(self) -> str: #?ownerproperty
pass 1 of 288@property89def owner(self⟨SavingsAccount D⟩) -> str: #?ownerproperty90 return self._ownerAlicedef balance(self) -> float: #?savingsgetter
111@property112def balance(self⟨SavingsAccount D⟩) -> float: #?savingsgetter113 return self._balance500.0print(f"Savings: {savings.owner}, balance=${savings.balance}")
157savings.balance = 500.0 #?setsavingsbalance158print(f"Savings: {savings.ownerAlice}, balance=${savings.balance500.0}")outputSavings: Alice, balance=$500.0def balance(self, value: float): #?savingssetter
pass 2 of 2115@balance.setter116def balance(self⟨SavingsAccount D⟩, value50.0: float): #?savingssetter117 if value < self.MINIMUM_BALANCE: #?checkminimum118 raise ValueError(f"Balance cannot be below ${self.MINIMUM_BALANCE}")if value < self.MINIMUM_BALANCE: #?checkminimum
116def balance(self, value: float): #?savingssetter117 if value50.0 < self.MINIMUM_BALANCE100.0: #?checkminimum118 raise ValueError(f"Balance cannot be below ${self.MINIMUM_BALANCE100.0}")119 self._balance = value #?setbalanceexcept ValueError as e: #?savingserror
162 savings.balance = 50.0 #?setbelowminimum163except ValueError as e: #?savingserror164 print(f"Error: {eBalance cannot be below $100.0}")outputError: Balance cannot be below $100.0print()
166print()167168checking = CheckingAccount("Bob") #?createchecking169checking.balance = 100.0 #?setcheckingbalanceself._owner ← Bob, self._balance ← 0.0
pass 2 of 284def __init__(self⟨CheckingAccount E⟩, ownerBob): #?accountinit85 self._owner→ Bob = ownerBob86 self._balance→ 0.0 = 0.0 #?balanceattrchecking ← ⟨CheckingAccount E⟩
168checking→ ⟨CheckingAccount E⟩ = CheckingAccount("Bob") #?createchecking169checking.balance = 100.0 #?setcheckingbalance170print(f"Checking: {checking.owner}, balance=${checking.balance}")self._balance ← 100.0
pass 1 of 3131@balance.setter132def balance(self⟨CheckingAccount E⟩, value100.0: float): #?checkingsetter133 if value < self.OVERDRAFT_LIMIT: #?checkoverdraft134 raise ValueError(f"Exceeded overdraft limit of ${-self.OVERDRAFT_LIMIT}")135 self._balance→ 100.0 = value100.0All 3 passes — pass 1 is the card above pass valueself.OVERDRAFT_LIMITeself._balance1 100.0 — — 100.0 2 -300.0 — — -300.0 3 -600.0 -500.0 Exceeded overdraft limit of $500.0 — checking.balance ← 100.0
168checking = CheckingAccount("Bob") #?createchecking169checking.balance→ 100.0 = 100.0 #?setcheckingbalance170print(f"Checking: {checking.ownerBob}, balance=${checking.balance100.0}")def owner(self) -> str: #?ownerproperty
pass 2 of 288@property89def owner(self⟨CheckingAccount E⟩) -> str: #?ownerproperty90 return self._ownerBobdef balance(self) -> float: #?checkinggetter
pass 1 of 2127@property128def balance(self⟨CheckingAccount E⟩) -> float: #?checkinggetter129 return self._balance100.0print(f"Checking: {checking.owner}, balance=${checking.balance}")
169checking.balance = 100.0 #?setcheckingbalance170print(f"Checking: {checking.ownerBob}, balance=${checking.balance100.0}")171172# Use overdraft #?useoverdraft173checking.balance = -300.0 #?setnegative174print(f"After overdraft: balance=${checking.balance}")outputChecking: Bob, balance=$100.0checking.balance ← -300.0
172# Use overdraft #?useoverdraft173checking.balance→ -300.0 = -300.0 #?setnegative174print(f"After overdraft: balance=${checking.balance-300.0}")def balance(self) -> float: #?checkinggetter
pass 2 of 2127@property128def balance(self⟨CheckingAccount E⟩) -> float: #?checkinggetter129 return self._balance-300.0print(f"After overdraft: balance=${checking.balance}")
173checking.balance = -300.0 #?setnegative174print(f"After overdraft: balance=${checking.balance-300.0}")outputAfter overdraft: balance=$-300.0if value < self.OVERDRAFT_LIMIT: #?checkoverdraft
132def balance(self, value: float): #?checkingsetter133 if value-600.0 < self.OVERDRAFT_LIMIT-500.0: #?checkoverdraft134 raise ValueError(f"Exceeded overdraft limit of ${-self.OVERDRAFT_LIMIT-500.0}")135 self._balance = valueexcept ValueError as e: #?overdrafterror
178 checking.balance = -600.0 #?exceedoverdraft179except ValueError as e: #?overdrafterror180 print(f"Error: {eExceeded overdraft limit of $500.0}")outputError: Exceeded overdraft limit of $500.0print(" === Abstract Property Rules ===")
182print("\n=== Abstract Property Rules ===")183print("""184Syntax for abstract property:185 @property186 @abstractmethod187 def my_property(self):188 pass189190Syntax for abstract setter:191 @my_property.setter192 @abstractmethod193 def my_property(self, value):194 pass195196Key points:1971. @property must come BEFORE @abstractmethod1982. Subclass must implement the property1993. Can have abstract getter only, or both getter and setter2004. Concrete properties work normally201""")output === Abstract Property Rules === Syntax for abstract property: @property @abstractmethod def my_property(self): pass Syntax for abstract setter: @my_property.setter @abstractmethod def my_property(self, value): pass Key points: 1. @property must come BEFORE @abstractmethod 2. Subclass must implement the property 3. Can have abstract getter only, or both getter and setter 4. Concrete properties work normally
Combine @property with @abstractmethod for required properties.
Multiple inheritance with ABC
Inherit from multiple abstract classes.
# Abstract Classes with Multiple Inheritance
from abc import ABC, abstractmethod
# Multiple abstract base classes
class Printable(ABC):
"""Abstract class for objects that can be printed."""
@abstractmethod
def to_string(self) -> str:
"""Convert to printable string."""
pass
def print(self):
"""Concrete method that uses to_string()."""
print(f"[PRINT] {self.to_string()}")
class Serializable(ABC):
"""Abstract class for objects that can be serialized."""
@abstractmethod
def to_dict(self) -> dict:
"""Convert to dictionary for serialization."""
pass
def to_json_string(self) -> str:
"""Concrete method that uses to_dict()."""
import json
return json.dumps(self.to_dict())
class Comparable(ABC):
"""Abstract class for objects that can be compared."""
@abstractmethod
def compare_to(self, other) -> int:
"""
Compare to another object.
Returns: negative if self < other, 0 if equal, positive if self > other
"""
pass
def __lt__(self, other):
return self.compare_to(other) < 0
def __le__(self, other):
return self.compare_to(other) <= 0
def __gt__(self, other):
return self.compare_to(other) > 0
def __ge__(self, other):
return self.compare_to(other) >= 0
def __eq__(self, other):
return self.compare_to(other) == 0
# Class inheriting from multiple abstract classes
class Product(Printable, Serializable, Comparable):
"""
Product implements all three abstract classes.
Must implement: to_string, to_dict, compare_to
"""
def __init__(self, name, price, quantity):
self.name = name
self.price = price
self.quantity = quantity
# From Printable
def to_string(self) -> str:
return f"{self.name}: ${self.price:.2f} (qty: {self.quantity})"
# From Serializable
def to_dict(self) -> dict:
return {
"name": self.name,
"price": self.price,
"quantity": self.quantity
}
# From Comparable
def compare_to(self, other) -> int:
# Compare by price
if self.price < other.price:
return -1
elif self.price > other.price:
return 1
return 0
# Another class with multiple inheritance
class Employee(Printable, Serializable):
"""Employee implements Printable and Serializable."""
def __init__(self, name, department, salary):
self.name = name
self.department = department
self.salary = salary
def to_string(self) -> str:
return f"{self.name} ({self.department}) - ${self.salary:,.2f}"
def to_dict(self) -> dict:
return {
"name": self.name,
"department": self.department,
"salary": self.salary
}
print("=== Multiple Inheritance with ABCs ===\n")
# Product examples
print("--- Products ---")
products = [
Product("Laptop", 999.99, 10),
Product("Mouse", 29.99, 50),
Product("Keyboard", 79.99, 30),
]
# Using Printable interface
print("Using Printable.print():")
for p in products:
p.print()
print()
# Using Serializable interface
print("Using Serializable.to_json_string():")
for p in products:
print(f" {p.to_json_string()}")
print()
# Using Comparable interface
print("Using Comparable (sorting by price):")
sorted_products = sorted(products)
for p in sorted_products:
print(f" {p.to_string()}")
print()
# Compare products directly
p1, p2 = products[0], products[1]
print(f"Comparing {p1.name} and {p2.name}:")
print(f" {p1.name} < {p2.name}: {p1 < p2}")
print(f" {p1.name} > {p2.name}: {p1 > p2}")
print(f" {p1.name} == {p2.name}: {p1 == p2}")
# Employee examples
print("\n--- Employees ---")
employees = [
Employee("Alice", "Engineering", 95000),
Employee("Bob", "Marketing", 75000),
]
for emp in employees:
emp.print()
print(f" JSON: {emp.to_json_string()}")
print("\n--- Method Resolution Order ---")
print(f"Product MRO: {[cls.__name__ for cls in Product.__mro__]}")
print(f"Employee MRO: {[cls.__name__ for cls in Employee.__mro__]}")
print("\n=== Multiple Inheritance Benefits ===")
print("""
1. Compose Behaviors:
- Printable: adds print capability
- Serializable: adds JSON serialization
- Comparable: adds comparison operators
2. Interface-like Pattern:
- Each ABC defines a small interface
- Classes mix and match as needed
3. Code Reuse:
- Concrete methods in ABCs provide default behavior
- Only abstract methods need implementation
4. Python's MRO (Method Resolution Order):
- Resolves diamond problem
- C3 linearization algorithm
- Use super() to delegate properly
""")
"""Abstract class for objects that can be printed."""
6class Printable(ABC): #?printableabc7 """Abstract class for objects that can be printed."""8 9 @abstractmethod10 def to_string(self) -> str: #?tostringabstract11 """Convert to printable string."""12 pass13 14 def print(self): #?printconcrete15 """Concrete method that uses to_string()."""16 print(f"[PRINT] {self.to_string()}")171819class Serializable(ABC): #?serializableabc20 """Abstract class for objects that can be serialized."""21 22 @abstractmethod23 def to_dict(self) -> dict: #?todictabstract24 """Convert to dictionary for serialization."""25 pass26 27 def to_json_string(self) -> str: #?tojsonconcrete28 """Concrete method that uses to_dict()."""29 import json30 return json.dumps(self.to_dict()) #?dumpsjson313233class Comparable(ABC): #?comparableabc34 """Abstract class for objects that can be compared."""35 36 @abstractmethod37 def compare_to(self, other) -> int: #?comparetoabstract38 """39 Compare to another object.40 Returns: negative if self < other, 0 if equal, positive if self > other41 """42 pass43 44 def __lt__(self, other): #?ltmethod45 return self.compare_to(other) < 046 47 def __le__(self, other): #?lemethod48 return self.compare_to(other) <= 049 50 def __gt__(self, other): #?gtmethod51 return self.compare_to(other) > 052 53 def __ge__(self, other): #?gemethod54 return self.compare_to(other) >= 055 56 def __eq__(self, other): #?eqmethod57 return self.compare_to(other) == 0585960# Class inheriting from multiple abstract classes #?multipleinheritance61class Product(Printable, Serializable, Comparable): #?productclass62 """63 Product implements all three abstract classes.64 Must implement: to_string, to_dict, compare_to65 """66 67 def __init__(self, name, price, quantity): #?productinit68 self.name = name69 self.price = price70 self.quantity = quantity71 72 # From Printable #?fromprintable73 def to_string(self) -> str: #?productostring74 return f"{self.name}: ${self.price:.2f} (qty: {self.quantity})"75 76 # From Serializable #?fromserializable77 def to_dict(self) -> dict: #?producttodict78 return {79 "name": self.name,80 "price": self.price,81 "quantity": self.quantity82 }83 84 # From Comparable #?fromcomparable85 def compare_to(self, other) -> int: #?productcompareto86 # Compare by price #?comparebyprice87 if self.price < other.price:88 return -189 elif self.price > other.price:90 return 191 return 0929394# Another class with multiple inheritance #?anotherclass95class Employee(Printable, Serializable): #?employeeclass96 """Employee implements Printable and Serializable."""97 98 def __init__(self, name, department, salary): #?employeeinit99 self.name = name100 self.department = department101 self.salary = salary102 103 def to_string(self) -> str: #?employeetostring104 return f"{self.name} ({self.department}) - ${self.salary:,.2f}"105 106 def to_dict(self) -> dict: #?employeetodict107 return {108 "name": self.name,109 "department": self.department,110 "salary": self.salary111 }112113114print("=== Multiple Inheritance with ABCs ===\n")115116# Product examples #?productdemo117print("--- Products ---")118products = [ #?productslist119 Product("Laptop", 999.99, 10),120 Product("Mouse", 29.99, 50),121 Product("Keyboard", 79.99, 30),122]output=== Multiple Inheritance with ABCs === --- Products ---self.name ← Laptop, self.price ← 999.99, self.quantity ← 10
pass 1 of 367def __init__(self⟨Product A⟩, nameLaptop, price999.99, quantity10): #?productinit68 self.name→ Laptop = nameLaptop69 self.price→ 999.99 = price999.9970 self.quantity→ 10 = quantity10All 3 passes — pass 1 is the card above pass selfnamepricequantityself.nameself.priceself.quantity1 ⟨Product A⟩ Laptop 999.99 10 Laptop 999.99 10 2 ⟨Product B⟩ Mouse 29.99 50 Mouse 29.99 50 3 ⟨Product C⟩ Keyboard 79.99 30 Keyboard 79.99 30 products ← [⟨Product A⟩, ⟨Product B⟩, ⟨Product C⟩]
117print("--- Products ---")118products→ [⟨Product A⟩, ⟨Product B⟩, ⟨Product C⟩] = [ #?productslist119 Product("Laptop", 999.99, 10),120 Product("Mouse", 29.99, 50),121 Product("Keyboard", 79.99, 30),122]123124# Using Printable interface #?useprintable125print("Using Printable.print():")126for p in products: #?iterateproductsoutputUsing Printable.print():for p in products: #?iterateproducts
pass 1 of 3125print("Using Printable.print():")126for p⟨Product A⟩ in products[⟨Product A⟩, ⟨Product B⟩, ⟨Product C⟩]: #?iterateproducts127 p⟨Product A⟩.print() #?callprintAll 3 passes — pass 1 is the card above pass p1 ⟨Product A⟩ 2 ⟨Product B⟩ 3 ⟨Product C⟩ def print(self): #?printconcrete
pass 1 of 514def print(self⟨Product A⟩): #?printconcrete15 """Concrete method that uses to_string()."""16 print(f"[PRINT] {self.to_string()}")All 5 passes — pass 1 is the card above pass selfself.nameself.departmentself.salary1 ⟨Product A⟩ — — — 2 ⟨Product B⟩ — — — 3 ⟨Product C⟩ — — — 4 ⟨Employee D⟩ Alice Engineering 95000 5 ⟨Employee E⟩ Bob Marketing 75000 def to_string(self) -> str: #?productostring
pass 1 of 672# From Printable #?fromprintable73def to_string(self⟨Product A⟩) -> str: #?productostring74 return f"{self.nameLaptop}: ${self.price999.99:.2f} (qty: {self.quantity10})"All 6 passes — pass 1 is the card above pass selfself.nameself.priceself.quantity1 ⟨Product A⟩ Laptop 999.99 10 2 ⟨Product B⟩ Mouse 29.99 50 3 ⟨Product C⟩ Keyboard 79.99 30 4 ⟨Product B⟩ Mouse 29.99 50 5 ⟨Product C⟩ Keyboard 79.99 30 6 ⟨Product A⟩ Laptop 999.99 10 p.print() #?callprint
15 """Concrete method that uses to_string()."""16 print(f"[PRINT] {self.to_string()}")171819class Serializable(ABC): #?serializableabc20 """Abstract class for objects that can be serialized."""21 22 @abstractmethod23 def to_dict(self) -> dict: #?todictabstract24 """Convert to dictionary for serialization."""25 pass26 27 def to_json_string(self) -> str: #?tojsonconcrete28 """Concrete method that uses to_dict()."""29 import json30 return json.dumps(self.to_dict()) #?dumpsjson313233class Comparable(ABC): #?comparableabc34 """Abstract class for objects that can be compared."""35 36 @abstractmethod37 def compare_to(self, other) -> int: #?comparetoabstract38 """39 Compare to another object.40 Returns: negative if self < other, 0 if equal, positive if self > other41 """42 pass43 44 def __lt__(self, other): #?ltmethod45 return self.compare_to(other) < 046 47 def __le__(self, other): #?lemethod48 return self.compare_to(other) <= 049 50 def __gt__(self, other): #?gtmethod51 return self.compare_to(other) > 052 53 def __ge__(self, other): #?gemethod54 return self.compare_to(other) >= 055 56 def __eq__(self, other): #?eqmethod57 return self.compare_to(other) == 0585960# Class inheriting from multiple abstract classes #?multipleinheritance61class Product(Printable, Serializable, Comparable): #?productclass62 """63 Product implements all three abstract classes.64 Must implement: to_string, to_dict, compare_to65 """66 67 def __init__(self, name, price, quantity): #?productinit68 self.name = name69 self.price = price70 self.quantity = quantity71 72 # From Printable #?fromprintable73 def to_string(self) -> str: #?productostring74 return f"{self.name}: ${self.price:.2f} (qty: {self.quantity})"75 76 # From Serializable #?fromserializable77 def to_dict(self) -> dict: #?producttodict78 return {79 "name": self.name,80 "price": self.price,81 "quantity": self.quantity82 }83 84 # From Comparable #?fromcomparable85 def compare_to(self, other) -> int: #?productcompareto86 # Compare by price #?comparebyprice87 if self.price < other.price:88 return -189 elif self.price > other.price:90 return 191 return 0929394# Another class with multiple inheritance #?anotherclass95class Employee(Printable, Serializable): #?employeeclass96 """Employee implements Printable and Serializable."""97 98 def __init__(self, name, department, salary): #?employeeinit99 self.name = name100 self.department = department101 self.salary = salary102 103 def to_string(self) -> str: #?employeetostring104 return f"{self.name} ({self.department}) - ${self.salary:,.2f}"105 106 def to_dict(self) -> dict: #?employeetodict107 return {108 "name": self.name,109 "department": self.department,110 "salary": self.salary111 }112113114print("=== Multiple Inheritance with ABCs ===\n")115116# Product examples #?productdemo117print("--- Products ---")118products = [ #?productslist119 Product("Laptop", 999.99, 10),120 Product("Mouse", 29.99, 50),121 Product("Keyboard", 79.99, 30),122]123124# Using Printable interface #?useprintable125print("Using Printable.print():")126for p in products: #?iterateproducts127 p⟨Product A⟩.print() #?callprintoutput[PRINT] Laptop: $999.99 (qty: 10)p.print() #?callprint
15 """Concrete method that uses to_string()."""16 print(f"[PRINT] {self.to_string()}")171819class Serializable(ABC): #?serializableabc20 """Abstract class for objects that can be serialized."""21 22 @abstractmethod23 def to_dict(self) -> dict: #?todictabstract24 """Convert to dictionary for serialization."""25 pass26 27 def to_json_string(self) -> str: #?tojsonconcrete28 """Concrete method that uses to_dict()."""29 import json30 return json.dumps(self.to_dict()) #?dumpsjson313233class Comparable(ABC): #?comparableabc34 """Abstract class for objects that can be compared."""35 36 @abstractmethod37 def compare_to(self, other) -> int: #?comparetoabstract38 """39 Compare to another object.40 Returns: negative if self < other, 0 if equal, positive if self > other41 """42 pass43 44 def __lt__(self, other): #?ltmethod45 return self.compare_to(other) < 046 47 def __le__(self, other): #?lemethod48 return self.compare_to(other) <= 049 50 def __gt__(self, other): #?gtmethod51 return self.compare_to(other) > 052 53 def __ge__(self, other): #?gemethod54 return self.compare_to(other) >= 055 56 def __eq__(self, other): #?eqmethod57 return self.compare_to(other) == 0585960# Class inheriting from multiple abstract classes #?multipleinheritance61class Product(Printable, Serializable, Comparable): #?productclass62 """63 Product implements all three abstract classes.64 Must implement: to_string, to_dict, compare_to65 """66 67 def __init__(self, name, price, quantity): #?productinit68 self.name = name69 self.price = price70 self.quantity = quantity71 72 # From Printable #?fromprintable73 def to_string(self) -> str: #?productostring74 return f"{self.name}: ${self.price:.2f} (qty: {self.quantity})"75 76 # From Serializable #?fromserializable77 def to_dict(self) -> dict: #?producttodict78 return {79 "name": self.name,80 "price": self.price,81 "quantity": self.quantity82 }83 84 # From Comparable #?fromcomparable85 def compare_to(self, other) -> int: #?productcompareto86 # Compare by price #?comparebyprice87 if self.price < other.price:88 return -189 elif self.price > other.price:90 return 191 return 0929394# Another class with multiple inheritance #?anotherclass95class Employee(Printable, Serializable): #?employeeclass96 """Employee implements Printable and Serializable."""97 98 def __init__(self, name, department, salary): #?employeeinit99 self.name = name100 self.department = department101 self.salary = salary102 103 def to_string(self) -> str: #?employeetostring104 return f"{self.name} ({self.department}) - ${self.salary:,.2f}"105 106 def to_dict(self) -> dict: #?employeetodict107 return {108 "name": self.name,109 "department": self.department,110 "salary": self.salary111 }112113114print("=== Multiple Inheritance with ABCs ===\n")115116# Product examples #?productdemo117print("--- Products ---")118products = [ #?productslist119 Product("Laptop", 999.99, 10),120 Product("Mouse", 29.99, 50),121 Product("Keyboard", 79.99, 30),122]123124# Using Printable interface #?useprintable125print("Using Printable.print():")126for p in products: #?iterateproducts127 p⟨Product B⟩.print() #?callprintoutput[PRINT] Mouse: $29.99 (qty: 50)p.print() #?callprint
15 """Concrete method that uses to_string()."""16 print(f"[PRINT] {self.to_string()}")171819class Serializable(ABC): #?serializableabc20 """Abstract class for objects that can be serialized."""21 22 @abstractmethod23 def to_dict(self) -> dict: #?todictabstract24 """Convert to dictionary for serialization."""25 pass26 27 def to_json_string(self) -> str: #?tojsonconcrete28 """Concrete method that uses to_dict()."""29 import json30 return json.dumps(self.to_dict()) #?dumpsjson313233class Comparable(ABC): #?comparableabc34 """Abstract class for objects that can be compared."""35 36 @abstractmethod37 def compare_to(self, other) -> int: #?comparetoabstract38 """39 Compare to another object.40 Returns: negative if self < other, 0 if equal, positive if self > other41 """42 pass43 44 def __lt__(self, other): #?ltmethod45 return self.compare_to(other) < 046 47 def __le__(self, other): #?lemethod48 return self.compare_to(other) <= 049 50 def __gt__(self, other): #?gtmethod51 return self.compare_to(other) > 052 53 def __ge__(self, other): #?gemethod54 return self.compare_to(other) >= 055 56 def __eq__(self, other): #?eqmethod57 return self.compare_to(other) == 0585960# Class inheriting from multiple abstract classes #?multipleinheritance61class Product(Printable, Serializable, Comparable): #?productclass62 """63 Product implements all three abstract classes.64 Must implement: to_string, to_dict, compare_to65 """66 67 def __init__(self, name, price, quantity): #?productinit68 self.name = name69 self.price = price70 self.quantity = quantity71 72 # From Printable #?fromprintable73 def to_string(self) -> str: #?productostring74 return f"{self.name}: ${self.price:.2f} (qty: {self.quantity})"75 76 # From Serializable #?fromserializable77 def to_dict(self) -> dict: #?producttodict78 return {79 "name": self.name,80 "price": self.price,81 "quantity": self.quantity82 }83 84 # From Comparable #?fromcomparable85 def compare_to(self, other) -> int: #?productcompareto86 # Compare by price #?comparebyprice87 if self.price < other.price:88 return -189 elif self.price > other.price:90 return 191 return 0929394# Another class with multiple inheritance #?anotherclass95class Employee(Printable, Serializable): #?employeeclass96 """Employee implements Printable and Serializable."""97 98 def __init__(self, name, department, salary): #?employeeinit99 self.name = name100 self.department = department101 self.salary = salary102 103 def to_string(self) -> str: #?employeetostring104 return f"{self.name} ({self.department}) - ${self.salary:,.2f}"105 106 def to_dict(self) -> dict: #?employeetodict107 return {108 "name": self.name,109 "department": self.department,110 "salary": self.salary111 }112113114print("=== Multiple Inheritance with ABCs ===\n")115116# Product examples #?productdemo117print("--- Products ---")118products = [ #?productslist119 Product("Laptop", 999.99, 10),120 Product("Mouse", 29.99, 50),121 Product("Keyboard", 79.99, 30),122]123124# Using Printable interface #?useprintable125print("Using Printable.print():")126for p in products: #?iterateproducts127 p⟨Product C⟩.print() #?callprintoutput[PRINT] Keyboard: $79.99 (qty: 30)print()
129print()130131# Using Serializable interface #?useserializable132print("Using Serializable.to_json_string():")133for p in products:outputUsing Serializable.to_json_string():for p in products:
pass 1 of 3132print("Using Serializable.to_json_string():")133for p⟨Product A⟩ in products[⟨Product A⟩, ⟨Product B⟩, ⟨Product C⟩]:134 print(f" {p⟨Product A⟩.to_json_string()}") #?calltojsonAll 3 passes — pass 1 is the card above pass p1 ⟨Product A⟩ 2 ⟨Product B⟩ 3 ⟨Product C⟩ def to_json_string(self) -> str: #?tojsonconcrete
pass 1 of 527def to_json_string(self⟨Product A⟩) -> str: #?tojsonconcrete28 """Concrete method that uses to_dict()."""29 import json30 return json<module 'json' from '/usr/local/lib/python3.12/json/__init__.py'>.dumps(self.to_dict()) #?dumpsjsonAll 5 passes — pass 1 is the card above pass selfself.nameself.departmentself.salary1 ⟨Product A⟩ — — — 2 ⟨Product B⟩ — — — 3 ⟨Product C⟩ — — — 4 ⟨Employee D⟩ Alice Engineering 95000 5 ⟨Employee E⟩ Bob Marketing 75000 def to_dict(self) -> dict: #?producttodict
pass 1 of 376# From Serializable #?fromserializable77def to_dict(self⟨Product A⟩) -> dict: #?producttodict78 return {79 "name": self.nameLaptop,80 "price": self.price999.99,81 "quantity": self.quantity1082 }All 3 passes — pass 1 is the card above pass selfself.nameself.priceself.quantity1 ⟨Product A⟩ Laptop 999.99 10 2 ⟨Product B⟩ Mouse 29.99 50 3 ⟨Product C⟩ Keyboard 79.99 30 print(f" {p.to_json_string()}") #?calltojson
133for p in products:134 print(f" {p⟨Product A⟩.to_json_string()}") #?calltojsonoutput {"name": "Laptop", "price": 999.99, "quantity": 10}print(f" {p.to_json_string()}") #?calltojson
133for p in products:134 print(f" {p⟨Product B⟩.to_json_string()}") #?calltojsonoutput {"name": "Mouse", "price": 29.99, "quantity": 50}print(f" {p.to_json_string()}") #?calltojson
133for p in products:134 print(f" {p⟨Product C⟩.to_json_string()}") #?calltojsonoutput {"name": "Keyboard", "price": 79.99, "quantity": 30}sorted_products = sorted(products) #?sortproducts
136print()137138# Using Comparable interface #?usecomparable139print("Using Comparable (sorting by price):")140sorted_products = sorted(products[⟨Product A⟩, ⟨Product B⟩, ⟨Product C⟩]) #?sortproducts141for p in sorted_products: #?printsortedoutputUsing Comparable (sorting by price):def __lt__(self, other): #?ltmethod
pass 1 of 544def __lt__(self⟨Product B⟩, other⟨Product A⟩): #?ltmethod45 return self.compare_to(other⟨Product A⟩) < 0All 5 passes — pass 1 is the card above pass selfotherself.priceother.price1 ⟨Product B⟩ ⟨Product A⟩ 29.99 999.99 2 ⟨Product C⟩ ⟨Product B⟩ — — 3 ⟨Product C⟩ ⟨Product A⟩ 79.99 999.99 4 ⟨Product C⟩ ⟨Product B⟩ — — 5 ⟨Product A⟩ ⟨Product B⟩ — — def compare_to(self, other) -> int: #?productcompareto # Compa…
pass 1 of 784# From Comparable #?fromcomparable85def compare_to(self⟨Product B⟩, other⟨Product A⟩) -> int: #?productcompareto86 # Compare by price #?comparebyprice87 if self.price < other.price:88 return -1All 7 passes — pass 1 is the card above pass selfotherself.priceother.price1 ⟨Product B⟩ ⟨Product A⟩ 29.99 999.99 2 ⟨Product C⟩ ⟨Product B⟩ — — 3 ⟨Product C⟩ ⟨Product A⟩ 79.99 999.99 4 ⟨Product C⟩ ⟨Product B⟩ — — 5 ⟨Product A⟩ ⟨Product B⟩ — — 6 ⟨Product A⟩ ⟨Product B⟩ — — 7 ⟨Product A⟩ ⟨Product B⟩ — — if self.price < other.price:
pass 1 of 286# Compare by price #?comparebyprice87if self.price29.99 < other.price999.99:88 return -189elif self.price > other.price:elif self.price > other.price:
pass 1 of 588 return -189elif self.price79.99 > other.price29.99:90 return 191return 0All 5 passes — pass 1 is the card above pass self.price1 79.99 2 79.99 3 999.99 4 999.99 5 999.99 if self.price < other.price:
pass 2 of 286# Compare by price #?comparebyprice87if self.price79.99 < other.price999.99:88 return -189elif self.price > other.price:sorted_products ← [⟨Product B⟩, ⟨Product C⟩, ⟨Product A⟩]
139print("Using Comparable (sorting by price):")140sorted_products→ [⟨Product B⟩, ⟨Product C⟩, ⟨Product A⟩] = sorted(products[⟨Product A⟩, ⟨Product B⟩, ⟨Product C⟩]) #?sortproducts141for p in sorted_products: #?printsortedfor p in sorted_products: #?printsorted
pass 1 of 3140sorted_products = sorted(products) #?sortproducts141for p⟨Product B⟩ in sorted_products[⟨Product B⟩, ⟨Product C⟩, ⟨Product A⟩]: #?printsorted142 print(f" {p⟨Product B⟩.to_string()}")All 3 passes — pass 1 is the card above pass p1 ⟨Product B⟩ 2 ⟨Product C⟩ 3 ⟨Product A⟩ print(f" {p.to_string()}")
141for p in sorted_products: #?printsorted142 print(f" {p⟨Product B⟩.to_string()}")output Mouse: $29.99 (qty: 50)print(f" {p.to_string()}")
141for p in sorted_products: #?printsorted142 print(f" {p⟨Product C⟩.to_string()}")output Keyboard: $79.99 (qty: 30)print(f" {p.to_string()}")
141for p in sorted_products: #?printsorted142 print(f" {p⟨Product A⟩.to_string()}")output Laptop: $999.99 (qty: 10)p1 ← ⟨Product A⟩, p2 ← ⟨Product B⟩
144print()145146# Compare products directly #?directcompare147p1→ ⟨Product A⟩, p2→ ⟨Product B⟩ = products[0]⟨Product A⟩, products[1]⟨Product B⟩ #?gettwo148print(f"Comparing {p1.nameLaptop} and {p2.nameMouse}:")149print(f" {p1.nameLaptop} < {p2.nameMouse}: {p1⟨Product A⟩ < p2⟨Product B⟩}") #?ltcompare150print(f" {p1.name} > {p2.name}: {p1 > p2}") #?gtcompareoutputComparing Laptop and Mouse:print(f" {p1.name} < {p2.name}: {p1 < p2}") #?ltcompare
148print(f"Comparing {p1.name} and {p2.name}:")149print(f" {p1.nameLaptop} < {p2.nameMouse}: {p1⟨Product A⟩ < p2⟨Product B⟩}") #?ltcompare150print(f" {p1.nameLaptop} > {p2.nameMouse}: {p1⟨Product A⟩ > p2⟨Product B⟩}") #?gtcompare151print(f" {p1.name} == {p2.name}: {p1 == p2}") #?eqcompareoutput Laptop < Mouse: Falsedef __gt__(self, other): #?gtmethod
50def __gt__(self⟨Product A⟩, other⟨Product B⟩): #?gtmethod51 return self.compare_to(other⟨Product B⟩) > 0print(f" {p1.name} > {p2.name}: {p1 > p2}") #?gtcompare
149print(f" {p1.name} < {p2.name}: {p1 < p2}") #?ltcompare150print(f" {p1.nameLaptop} > {p2.nameMouse}: {p1⟨Product A⟩ > p2⟨Product B⟩}") #?gtcompare151print(f" {p1.nameLaptop} == {p2.nameMouse}: {p1⟨Product A⟩ == p2⟨Product B⟩}") #?eqcompareoutput Laptop > Mouse: Truedef __eq__(self, other): #?eqmethod
56def __eq__(self⟨Product A⟩, other⟨Product B⟩): #?eqmethod57 return self.compare_to(other⟨Product B⟩) == 0print(f" {p1.name} == {p2.name}: {p1 == p2}") #?eqcompare
150print(f" {p1.name} > {p2.name}: {p1 > p2}") #?gtcompare151print(f" {p1.nameLaptop} == {p2.nameMouse}: {p1⟨Product A⟩ == p2⟨Product B⟩}") #?eqcompare152153# Employee examples #?employeedemo154print("\n--- Employees ---")155employees = [156 Employee("Alice", "Engineering", 95000),157 Employee("Bob", "Marketing", 75000),158]output Laptop == Mouse: False --- Employees ---self.name ← Alice, self.department ← Engineering, self.salary ← 95000
pass 1 of 298def __init__(self⟨Employee D⟩, nameAlice, departmentEngineering, salary95000): #?employeeinit99 self.name→ Alice = nameAlice100 self.department→ Engineering = departmentEngineering101 self.salary→ 95000 = salary95000self.name ← Bob, self.department ← Marketing, self.salary ← 75000
pass 2 of 298def __init__(self⟨Employee E⟩, nameBob, departmentMarketing, salary75000): #?employeeinit99 self.name→ Bob = nameBob100 self.department→ Marketing = departmentMarketing101 self.salary→ 75000 = salary75000employees ← [⟨Employee D⟩, ⟨Employee E⟩]
154print("\n--- Employees ---")155employees→ [⟨Employee D⟩, ⟨Employee E⟩] = [156 Employee("Alice", "Engineering", 95000),157 Employee("Bob", "Marketing", 75000),158]for emp in employees: #?iterateemployees
pass 1 of 2160for emp⟨Employee D⟩ in employees[⟨Employee D⟩, ⟨Employee E⟩]: #?iterateemployees161 emp⟨Employee D⟩.print() #?empprint162 print(f" JSON: {emp.to_json_string()}") #?empjsondef to_string(self) -> str: #?employeetostring
pass 1 of 2103def to_string(self⟨Employee D⟩) -> str: #?employeetostring104 return f"{self.nameAlice} ({self.departmentEngineering}) - ${self.salary95000:,.2f}"emp.print() #?empprint
15 """Concrete method that uses to_string()."""16 print(f"[PRINT] {self.to_string()}")171819class Serializable(ABC): #?serializableabc20 """Abstract class for objects that can be serialized."""21 22 @abstractmethod23 def to_dict(self) -> dict: #?todictabstract24 """Convert to dictionary for serialization."""25 pass26 27 def to_json_string(self) -> str: #?tojsonconcrete28 """Concrete method that uses to_dict()."""29 import json30 return json.dumps(self.to_dict()) #?dumpsjson313233class Comparable(ABC): #?comparableabc34 """Abstract class for objects that can be compared."""35 36 @abstractmethod37 def compare_to(self, other) -> int: #?comparetoabstract38 """39 Compare to another object.40 Returns: negative if self < other, 0 if equal, positive if self > other41 """42 pass43 44 def __lt__(self, other): #?ltmethod45 return self.compare_to(other) < 046 47 def __le__(self, other): #?lemethod48 return self.compare_to(other) <= 049 50 def __gt__(self, other): #?gtmethod51 return self.compare_to(other) > 052 53 def __ge__(self, other): #?gemethod54 return self.compare_to(other) >= 055 56 def __eq__(self, other): #?eqmethod57 return self.compare_to(other) == 0585960# Class inheriting from multiple abstract classes #?multipleinheritance61class Product(Printable, Serializable, Comparable): #?productclass62 """63 Product implements all three abstract classes.64 Must implement: to_string, to_dict, compare_to65 """66 67 def __init__(self, name, price, quantity): #?productinit68 self.name = name69 self.price = price70 self.quantity = quantity71 72 # From Printable #?fromprintable73 def to_string(self) -> str: #?productostring74 return f"{self.name}: ${self.price:.2f} (qty: {self.quantity})"75 76 # From Serializable #?fromserializable77 def to_dict(self) -> dict: #?producttodict78 return {79 "name": self.name,80 "price": self.price,81 "quantity": self.quantity82 }83 84 # From Comparable #?fromcomparable85 def compare_to(self, other) -> int: #?productcompareto86 # Compare by price #?comparebyprice87 if self.price < other.price:88 return -189 elif self.price > other.price:90 return 191 return 0929394# Another class with multiple inheritance #?anotherclass95class Employee(Printable, Serializable): #?employeeclass96 """Employee implements Printable and Serializable."""97 98 def __init__(self, name, department, salary): #?employeeinit99 self.name = name100 self.department = department101 self.salary = salary102 103 def to_string(self) -> str: #?employeetostring104 return f"{self.name} ({self.department}) - ${self.salary:,.2f}"105 106 def to_dict(self) -> dict: #?employeetodict107 return {108 "name": self.name,109 "department": self.department,110 "salary": self.salary111 }112113114print("=== Multiple Inheritance with ABCs ===\n")115116# Product examples #?productdemo117print("--- Products ---")118products = [ #?productslist119 Product("Laptop", 999.99, 10),120 Product("Mouse", 29.99, 50),121 Product("Keyboard", 79.99, 30),122]123124# Using Printable interface #?useprintable125print("Using Printable.print():")126for p in products: #?iterateproducts127 p.print() #?callprint128129print()130131# Using Serializable interface #?useserializable132print("Using Serializable.to_json_string():")133for p in products:134 print(f" {p.to_json_string()}") #?calltojson135136print()137138# Using Comparable interface #?usecomparable139print("Using Comparable (sorting by price):")140sorted_products = sorted(products) #?sortproducts141for p in sorted_products: #?printsorted142 print(f" {p.to_string()}")143144print()145146# Compare products directly #?directcompare147p1, p2 = products[0], products[1] #?gettwo148print(f"Comparing {p1.name} and {p2.name}:")149print(f" {p1.name} < {p2.name}: {p1 < p2}") #?ltcompare150print(f" {p1.name} > {p2.name}: {p1 > p2}") #?gtcompare151print(f" {p1.name} == {p2.name}: {p1 == p2}") #?eqcompare152153# Employee examples #?employeedemo154print("\n--- Employees ---")155employees = [156 Employee("Alice", "Engineering", 95000),157 Employee("Bob", "Marketing", 75000),158]159160for emp in employees: #?iterateemployees161 emp⟨Employee D⟩.print() #?empprint162 print(f" JSON: {emp⟨Employee D⟩.to_json_string()}") #?empjsonoutput[PRINT] Alice (Engineering) - $95,000.00def to_dict(self) -> dict: #?employeetodict
pass 1 of 2106def to_dict(self⟨Employee D⟩) -> dict: #?employeetodict107 return {108 "name": self.nameAlice,109 "department": self.departmentEngineering,110 "salary": self.salary95000111 }print(f" JSON: {emp.to_json_string()}") #?empjson
161emp.print() #?empprint162print(f" JSON: {emp⟨Employee D⟩.to_json_string()}") #?empjsonoutput JSON: {"name": "Alice", "department": "Engineering", "salary": 95000}for emp in employees: #?iterateemployees
pass 2 of 2160for emp⟨Employee E⟩ in employees[⟨Employee D⟩, ⟨Employee E⟩]: #?iterateemployees161 emp⟨Employee E⟩.print() #?empprint162 print(f" JSON: {emp.to_json_string()}") #?empjsondef to_string(self) -> str: #?employeetostring
pass 2 of 2103def to_string(self⟨Employee E⟩) -> str: #?employeetostring104 return f"{self.nameBob} ({self.departmentMarketing}) - ${self.salary75000:,.2f}"emp.print() #?empprint
15 """Concrete method that uses to_string()."""16 print(f"[PRINT] {self.to_string()}")171819class Serializable(ABC): #?serializableabc20 """Abstract class for objects that can be serialized."""21 22 @abstractmethod23 def to_dict(self) -> dict: #?todictabstract24 """Convert to dictionary for serialization."""25 pass26 27 def to_json_string(self) -> str: #?tojsonconcrete28 """Concrete method that uses to_dict()."""29 import json30 return json.dumps(self.to_dict()) #?dumpsjson313233class Comparable(ABC): #?comparableabc34 """Abstract class for objects that can be compared."""35 36 @abstractmethod37 def compare_to(self, other) -> int: #?comparetoabstract38 """39 Compare to another object.40 Returns: negative if self < other, 0 if equal, positive if self > other41 """42 pass43 44 def __lt__(self, other): #?ltmethod45 return self.compare_to(other) < 046 47 def __le__(self, other): #?lemethod48 return self.compare_to(other) <= 049 50 def __gt__(self, other): #?gtmethod51 return self.compare_to(other) > 052 53 def __ge__(self, other): #?gemethod54 return self.compare_to(other) >= 055 56 def __eq__(self, other): #?eqmethod57 return self.compare_to(other) == 0585960# Class inheriting from multiple abstract classes #?multipleinheritance61class Product(Printable, Serializable, Comparable): #?productclass62 """63 Product implements all three abstract classes.64 Must implement: to_string, to_dict, compare_to65 """66 67 def __init__(self, name, price, quantity): #?productinit68 self.name = name69 self.price = price70 self.quantity = quantity71 72 # From Printable #?fromprintable73 def to_string(self) -> str: #?productostring74 return f"{self.name}: ${self.price:.2f} (qty: {self.quantity})"75 76 # From Serializable #?fromserializable77 def to_dict(self) -> dict: #?producttodict78 return {79 "name": self.name,80 "price": self.price,81 "quantity": self.quantity82 }83 84 # From Comparable #?fromcomparable85 def compare_to(self, other) -> int: #?productcompareto86 # Compare by price #?comparebyprice87 if self.price < other.price:88 return -189 elif self.price > other.price:90 return 191 return 0929394# Another class with multiple inheritance #?anotherclass95class Employee(Printable, Serializable): #?employeeclass96 """Employee implements Printable and Serializable."""97 98 def __init__(self, name, department, salary): #?employeeinit99 self.name = name100 self.department = department101 self.salary = salary102 103 def to_string(self) -> str: #?employeetostring104 return f"{self.name} ({self.department}) - ${self.salary:,.2f}"105 106 def to_dict(self) -> dict: #?employeetodict107 return {108 "name": self.name,109 "department": self.department,110 "salary": self.salary111 }112113114print("=== Multiple Inheritance with ABCs ===\n")115116# Product examples #?productdemo117print("--- Products ---")118products = [ #?productslist119 Product("Laptop", 999.99, 10),120 Product("Mouse", 29.99, 50),121 Product("Keyboard", 79.99, 30),122]123124# Using Printable interface #?useprintable125print("Using Printable.print():")126for p in products: #?iterateproducts127 p.print() #?callprint128129print()130131# Using Serializable interface #?useserializable132print("Using Serializable.to_json_string():")133for p in products:134 print(f" {p.to_json_string()}") #?calltojson135136print()137138# Using Comparable interface #?usecomparable139print("Using Comparable (sorting by price):")140sorted_products = sorted(products) #?sortproducts141for p in sorted_products: #?printsorted142 print(f" {p.to_string()}")143144print()145146# Compare products directly #?directcompare147p1, p2 = products[0], products[1] #?gettwo148print(f"Comparing {p1.name} and {p2.name}:")149print(f" {p1.name} < {p2.name}: {p1 < p2}") #?ltcompare150print(f" {p1.name} > {p2.name}: {p1 > p2}") #?gtcompare151print(f" {p1.name} == {p2.name}: {p1 == p2}") #?eqcompare152153# Employee examples #?employeedemo154print("\n--- Employees ---")155employees = [156 Employee("Alice", "Engineering", 95000),157 Employee("Bob", "Marketing", 75000),158]159160for emp in employees: #?iterateemployees161 emp⟨Employee E⟩.print() #?empprint162 print(f" JSON: {emp⟨Employee E⟩.to_json_string()}") #?empjsonoutput[PRINT] Bob (Marketing) - $75,000.00def to_dict(self) -> dict: #?employeetodict
pass 2 of 2106def to_dict(self⟨Employee E⟩) -> dict: #?employeetodict107 return {108 "name": self.nameBob,109 "department": self.departmentMarketing,110 "salary": self.salary75000111 }print(f" JSON: {emp.to_json_string()}") #?empjson
161emp.print() #?empprint162print(f" JSON: {emp⟨Employee E⟩.to_json_string()}") #?empjsonoutput JSON: {"name": "Bob", "department": "Marketing", "salary": 75000}print(f"Product MRO: {[cls.__name__ for cls in Product.__mro__]}") #?p…
164print("\n--- Method Resolution Order ---")165print(f"Product MRO: {[cls.__name__(empty) for cls in Product.__mro__(<class '__main__.Product'>, <class '__main__.Printable'>, <class '__main__.Serializable'>, <class '__main__.Comparable'>, <class 'abc.ABC'>, <class 'object'>)]}") #?productmro166print(f"Employee MRO: {[cls.__name__(empty) for cls in Employee.__mro__(<class '__main__.Employee'>, <class '__main__.Printable'>, <class '__main__.Serializable'>, <class 'abc.ABC'>, <class 'object'>)]}") #?employeemro167168print("\n=== Multiple Inheritance Benefits ===")169print("""1701. Compose Behaviors:171 - Printable: adds print capability172 - Serializable: adds JSON serialization173 - Comparable: adds comparison operators1741752. Interface-like Pattern:176 - Each ABC defines a small interface177 - Classes mix and match as needed1781793. Code Reuse:180 - Concrete methods in ABCs provide default behavior181 - Only abstract methods need implementation1821834. Python's MRO (Method Resolution Order):184 - Resolves diamond problem185 - C3 linearization algorithm186 - Use super() to delegate properly187""")output --- Method Resolution Order --- Product MRO: ['Product', 'Printable', 'Serializable', 'Comparable', 'ABC', 'object'] Employee MRO: ['Employee', 'Printable', 'Serializable', 'ABC', 'object'] === Multiple Inheritance Benefits === 1. Compose Behaviors: - Printable: adds print capability - Serializable: adds JSON serialization - Comparable: adds comparison operators 2. Interface-like Pattern: - Each ABC defines a small interface - Classes mix and match as needed 3. Code Reuse: - Concrete methods in ABCs provide default behavior - Only abstract methods need implementation 4. Python's MRO (Method Resolution Order): - Resolves diamond problem - C3 linearization algorithm - Use super() to delegate properly
Class can implement multiple ABCs. Must implement all abstract methods.
Exercise: practical.py
Build a plugin system with abstract base classes