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_abc.py
Replay: real traced execution (multi-file project)
# 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!")

  1. 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") #?createcat
    output=== Basic Abstract Classes ===
  2. self.name ← Buddy

    pass 1 of 3
    12def __init__(self⟨Dog A⟩, nameBuddy): #?abcinit13    self.name→ Buddy = nameBuddy
    All 3 passes — pass 1 is the card above
    passselfnameself.name
    1⟨Dog A⟩BuddyBuddy
    2⟨Cat B⟩WhiskersWhiskers
    3⟨Bird C⟩TweetyTweety
  3. dog ← ⟨Dog A⟩

    59# Creating concrete instances #?createinstances60dog→ ⟨Dog A⟩ = Dog("Buddy") #?createdog61cat = Cat("Whiskers") #?createcat62bird = Bird("Tweety") #?createbird
  4. cat ← ⟨Cat B⟩

    60dog = Dog("Buddy") #?createdog61cat→ ⟨Cat B⟩ = Cat("Whiskers") #?createcat62bird = Bird("Tweety") #?createbird
  5. bird ← ⟨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⟩] #?animalslist
  6. for animal in animals: #?iterateanimals

    pass 1 of 3
    67for 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()}") #?callmove
    outputDog:
    All 3 passes — pass 1 is the card above
    passanimalselfself.name
    1⟨Dog A⟩⟨Dog A⟩Buddy
    2⟨Cat B⟩⟨Cat B⟩Whiskers
    3⟨Bird C⟩⟨Bird C⟩Tweety
  7. def speak(self): #?dogspeak

    30def speak(self⟨Dog A⟩): #?dogspeak31    return f"{self.nameBuddy} says: Woof!"
  8. 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!
  9. def move(self): #?dogmove

    33def move(self⟨Dog A⟩): #?dogmove34    return f"{self.nameBuddy} runs on four legs"
  10. print(f" {animal.move()}") #?callmove

    69print(f"  {animal.speak()}") #?callspeak70print(f"  {animal⟨Dog A⟩.move()}") #?callmove71print()
    output  Buddy runs on four legs
  11. def speak(self): #?catspeak

    40def speak(self⟨Cat B⟩): #?catspeak41    return f"{self.nameWhiskers} says: Meow!"
  12. 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!
  13. def move(self): #?catmove

    43def move(self⟨Cat B⟩): #?catmove44    return f"{self.nameWhiskers} walks gracefully"
  14. print(f" {animal.move()}") #?callmove

    69print(f"  {animal.speak()}") #?callspeak70print(f"  {animal⟨Cat B⟩.move()}") #?callmove71print()
    output  Whiskers walks gracefully
  15. def speak(self): #?birdspeak

    50def speak(self⟨Bird C⟩): #?birdspeak51    return f"{self.nameTweety} says: Tweet!"
  16. 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!
  17. def move(self): #?birdmove

    53def move(self⟨Bird C⟩): #?birdmove54    return f"{self.nameTweety} flies through the air"
  18. print(f" {animal.move()}") #?callmove

    69print(f"  {animal.speak()}") #?callspeak70print(f"  {animal⟨Bird C⟩.move()}") #?callmove71print()
    output  Tweety flies through the air
  19. print("--- Trying to instantiate abstract class ---")

    73# Try to instantiate abstract class #?tryabstract74print("--- Trying to instantiate abstract class ---")75try:
    output--- Trying to instantiate abstract class ---
  20. 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!
  21. 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 ---
  22. 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.

ABC Abstract Base Class. Can't be instantiated. May have abstract methods.

Abstract methods

Methods that subclasses must implement.

shapes
abstract_methods.py
Replay: real traced execution (multi-file project)
# 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
""")

  1. 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) #?createrectangle
    output=== Abstract Methods ===
  2. self.radius ← 5

    33def __init__(self⟨Circle A⟩, radius5): #?circleinit34    self.radius→ 5 = radius5
  3. circle ← ⟨Circle A⟩

    98# Create shapes #?createshapes99circle→ ⟨Circle A⟩ = Circle(5) #?createcircle100rectangle = Rectangle(4, 6) #?createrectangle101triangle = Triangle(3, 4, 5) #?createtriangle
  4. self.width ← 4, self.height ← 6

    49def __init__(self⟨Rectangle B⟩, width4, height6): #?rectangleinit50    self.width→ 4 = width451    self.height→ 6 = height6
  5. rectangle ← ⟨Rectangle B⟩

    99circle = Circle(5) #?createcircle100rectangle→ ⟨Rectangle B⟩ = Rectangle(4, 6) #?createrectangle101triangle = Triangle(3, 4, 5) #?createtriangle
  6. self.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 = c5
  7. triangle ← ⟨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]
  8. for shape in shapes: #?iterateshapes

    pass 1 of 3
    106# 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
    passshapeself
    1⟨Circle A⟩⟨Circle A⟩
    2⟨Rectangle B⟩⟨Rectangle B⟩
    3⟨Triangle C⟩⟨Triangle C⟩
  9. def describe_shape(shape: Shape): #?describeshape

    pass 1 of 3
    83# 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}") #?callarea
    All 3 passes — pass 1 is the card above
    passshapeself
    1⟨Circle A⟩⟨Circle A⟩
    2⟨Rectangle B⟩⟨Rectangle B⟩
    3⟨Triangle C⟩⟨Triangle C⟩
  10. def name(self) -> str: #?circlename

    pass 1 of 2
    42def name(self⟨Circle A⟩) -> str: #?circlename43    return "Circle"
  11. 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}") #?callperimeter
    outputShape: Circle
  12. def area(self) -> float: #?circlearea

    pass 1 of 2
    36def area(self⟨Circle A⟩) -> float: #?circlearea37    return Circle.PI3.14159 * self.radius5 ** 2
  13. print(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}") #?callperimeter
    output  Area: 78.54
  14. def perimeter(self) -> float: #?circleperimeter

    39def perimeter(self⟨Circle A⟩) -> float: #?circleperimeter40    return 2 * Circle.PI3.14159 * self.radius5
  15. print(f" Perimeter: {shape.perimeter():.2f}") #?callperimeter

    87print(f"  Area: {shape.area():.2f}") #?callarea88print(f"  Perimeter: {shape⟨Circle A⟩.perimeter():.2f}") #?callperimeter
    output  Perimeter: 31.42
  16. describe_shape(shape) #?calldescribe

    107for shape in shapes: #?iterateshapes108    describe_shape(shape⟨Circle A⟩) #?calldescribe109    print()
  17. def name(self) -> str: #?rectanglename

    pass 1 of 2
    59def name(self⟨Rectangle B⟩) -> str: #?rectanglename60    return "Rectangle"
  18. 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}") #?callperimeter
    outputShape: Rectangle
  19. def area(self) -> float: #?rectanglearea

    pass 1 of 2
    53def area(self⟨Rectangle B⟩) -> float: #?rectanglearea54    return self.width4 * self.height6
  20. print(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}") #?callperimeter
    output  Area: 24.00
  21. def perimeter(self) -> float: #?rectangleperimeter

    56def perimeter(self⟨Rectangle B⟩) -> float: #?rectangleperimeter57    return 2 * (self.width4 + self.height6)
  22. print(f" Perimeter: {shape.perimeter():.2f}") #?callperimeter

    87print(f"  Area: {shape.area():.2f}") #?callarea88print(f"  Perimeter: {shape⟨Rectangle B⟩.perimeter():.2f}") #?callperimeter
    output  Perimeter: 20.00
  23. describe_shape(shape) #?calldescribe

    107for shape in shapes: #?iterateshapes108    describe_shape(shape⟨Rectangle B⟩) #?calldescribe109    print()
  24. def name(self) -> str: #?trianglename

    pass 1 of 2
    79def name(self⟨Triangle C⟩) -> str: #?trianglename80    return "Triangle"
  25. 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}") #?callperimeter
    outputShape: Triangle
  26. s ← 6.0

    pass 1 of 2
    71def 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.5
  27. print(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}") #?callperimeter
    output  Area: 6.00
  28. def perimeter(self) -> float: #?triangleperimeter

    76def perimeter(self⟨Triangle C⟩) -> float: #?triangleperimeter77    return self.a3 + self.b4 + self.c5
  29. print(f" Perimeter: {shape.perimeter():.2f}") #?callperimeter

    87print(f"  Area: {shape.area():.2f}") #?callarea88print(f"  Perimeter: {shape⟨Triangle C⟩.perimeter():.2f}") #?callperimeter
    output  Perimeter: 12.00
  30. describe_shape(shape) #?calldescribe

    107for shape in shapes: #?iterateshapes108    describe_shape(shape⟨Triangle C⟩) #?calldescribe109    print()
  31. 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}")
  32. 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⟩]) #?sumarea
  33. def area(self) -> float: #?circlearea

    pass 2 of 2
    36def area(self⟨Circle A⟩) -> float: #?circlearea37    return Circle.PI3.14159 * self.radius5 ** 2
  34. def area(self) -> float: #?rectanglearea

    pass 2 of 2
    53def area(self⟨Rectangle B⟩) -> float: #?rectanglearea54    return self.width4 * self.height6
  35. s ← 6.0

    pass 2 of 2
    71def 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.5
  36. total ← 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 ---
  37. for shape in shapes:

    pass 1 of 3
    123print("\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'>)}") #?isinstanceshape
    All 3 passes — pass 1 is the card above
    passshapeself
    1⟨Circle A⟩⟨Circle A⟩
    2⟨Rectangle B⟩⟨Rectangle B⟩
    3⟨Triangle C⟩⟨Triangle C⟩
  38. def name(self) -> str: #?circlename

    pass 2 of 2
    42def name(self⟨Circle A⟩) -> str: #?circlename43    return "Circle"
  39. 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'>)}") #?isinstanceshape
    outputCircle is a Shape: True
  40. def name(self) -> str: #?rectanglename

    pass 2 of 2
    59def name(self⟨Rectangle B⟩) -> str: #?rectanglename60    return "Rectangle"
  41. 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'>)}") #?isinstanceshape
    outputRectangle is a Shape: True
  42. def name(self) -> str: #?trianglename

    pass 2 of 2
    79def name(self⟨Triangle C⟩) -> str: #?trianglename80    return "Triangle"
  43. 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'>)}") #?isinstanceshape
    outputTriangle is a Shape: True
  44. print(" === 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
  1. 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 ===
  2. self.radius ← 5

    33def __init__(self⟨Circle A⟩, radius5):34    self.radius→ 5 = radius5
  3. circle ← ⟨Circle A⟩

    98# Create shapes99circle→ ⟨Circle A⟩ = Circle(5)100rectangle = Rectangle(4, 6)101triangle = Triangle(3, 4, 5)
  4. self.width ← 4, self.height ← 6

    49def __init__(self⟨Rectangle B⟩, width4, height6):50    self.width→ 4 = width451    self.height→ 6 = height6
  5. rectangle ← ⟨Rectangle B⟩

    99circle = Circle(5)100rectangle→ ⟨Rectangle B⟩ = Rectangle(4, 6)101triangle = Triangle(3, 4, 5)
  6. 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 = c5
  7. triangle ← ⟨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⟩]
  8. for shape in shapes:

    pass 1 of 2
    105# Describe each shape106for shape⟨Circle A⟩ in shapes[⟨Circle A⟩, ⟨Rectangle B⟩]:107    describe_shape(shape⟨Circle A⟩)108    print()
  9. def describe_shape(shape: Shape):

    pass 1 of 2
    83# 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}")
  10. def name(self) -> str:

    pass 1 of 2
    42def name(self⟨Circle A⟩) -> str:43    return "Circle"
  11. 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: Circle
  12. def area(self) -> float:

    pass 1 of 2
    36def area(self⟨Circle A⟩) -> float:37    return Circle.PI3.14159 * self.radius5 ** 2
  13. print(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.54
  14. def perimeter(self) -> float:

    39def perimeter(self⟨Circle A⟩) -> float:40    return 2 * Circle.PI3.14159 * self.radius5
  15. print(f" Perimeter: {shape.perimeter():.2f}")

    87print(f"  Area: {shape.area():.2f}")88print(f"  Perimeter: {shape⟨Circle A⟩.perimeter():.2f}")
    output  Perimeter: 31.42
  16. describe_shape(shape)

    106for shape in shapes:107    describe_shape(shape⟨Circle A⟩)108    print()
  17. for shape in shapes:

    pass 2 of 2
    105# Describe each shape106for shape⟨Rectangle B⟩ in shapes[⟨Circle A⟩, ⟨Rectangle B⟩]:107    describe_shape(shape⟨Rectangle B⟩)108    print()
  18. def describe_shape(shape: Shape):

    pass 2 of 2
    83# 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}")
  19. def name(self) -> str:

    pass 1 of 2
    59def name(self⟨Rectangle B⟩) -> str:60    return "Rectangle"
  20. 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: Rectangle
  21. def area(self) -> float:

    pass 1 of 2
    53def area(self⟨Rectangle B⟩) -> float:54    return self.width4 * self.height6
  22. print(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.00
  23. def perimeter(self) -> float:

    56def perimeter(self⟨Rectangle B⟩) -> float:57    return 2 * (self.width4 + self.height6)
  24. print(f" Perimeter: {shape.perimeter():.2f}")

    87print(f"  Area: {shape.area():.2f}")88print(f"  Perimeter: {shape⟨Rectangle B⟩.perimeter():.2f}")
    output  Perimeter: 20.00
  25. describe_shape(shape)

    106for shape in shapes:107    describe_shape(shape⟨Rectangle B⟩)108    print()
  26. total = total_area(shapes)

    110# Calculate total area111total = total_area(shapes[⟨Circle A⟩, ⟨Rectangle B⟩])112print(f"Total area of all shapes: {total:.2f}")
  27. 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⟩])
  28. def area(self) -> float:

    pass 2 of 2
    36def area(self⟨Circle A⟩) -> float:37    return Circle.PI3.14159 * self.radius5 ** 2
  29. def area(self) -> float:

    pass 2 of 2
    53def area(self⟨Rectangle B⟩) -> float:54    return self.width4 * self.height6
  30. total ← 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 ---
  31. for shape in shapes:

    pass 1 of 2
    122print("\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'>)}")
  32. def name(self) -> str:

    pass 2 of 2
    42def name(self⟨Circle A⟩) -> str:43    return "Circle"
  33. 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: True
  34. for shape in shapes:

    pass 2 of 2
    122print("\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'>)}")
  35. def name(self) -> str:

    pass 2 of 2
    59def name(self⟨Rectangle B⟩) -> str:60    return "Rectangle"
  36. 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: True
  37. print(" === 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
  1. 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 ===
  2. self.radius ← 5

    33def __init__(self⟨Circle A⟩, radius5):34    self.radius→ 5 = radius5
  3. circle ← ⟨Circle A⟩

    98# Create shapes99circle→ ⟨Circle A⟩ = Circle(5)100rectangle = Rectangle(4, 6)101triangle = Triangle(3, 4, 5)
  4. self.width ← 4, self.height ← 6

    49def __init__(self⟨Rectangle B⟩, width4, height6):50    self.width→ 4 = width451    self.height→ 6 = height6
  5. rectangle ← ⟨Rectangle B⟩

    99circle = Circle(5)100rectangle→ ⟨Rectangle B⟩ = Rectangle(4, 6)101triangle = Triangle(3, 4, 5)
  6. 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 = c5
  7. triangle ← ⟨Triangle C⟩, shapes ← [⟨Triangle C⟩]

    100rectangle = Rectangle(4, 6)101triangle→ ⟨Triangle C⟩ = Triangle(3, 4, 5)102103shapes→ [⟨Triangle C⟩] = [triangle⟨Triangle C⟩]
  8. for shape in shapes:

    105# Describe each shape106for shape⟨Triangle C⟩ in shapes[⟨Triangle C⟩]:107    describe_shape(shape⟨Triangle C⟩)108    print()
  9. 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}")
  10. def name(self) -> str:

    pass 1 of 2
    79def name(self⟨Triangle C⟩) -> str:80    return "Triangle"
  11. 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: Triangle
  12. s ← 6.0

    pass 1 of 2
    71def 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.5
  13. print(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.00
  14. def perimeter(self) -> float:

    76def perimeter(self⟨Triangle C⟩) -> float:77    return self.a3 + self.b4 + self.c5
  15. print(f" Perimeter: {shape.perimeter():.2f}")

    87print(f"  Area: {shape.area():.2f}")88print(f"  Perimeter: {shape⟨Triangle C⟩.perimeter():.2f}")
    output  Perimeter: 12.00
  16. describe_shape(shape)

    106for shape in shapes:107    describe_shape(shape⟨Triangle C⟩)108    print()
  17. total = total_area(shapes)

    110# Calculate total area111total = total_area(shapes[⟨Triangle C⟩])112print(f"Total area of all shapes: {total:.2f}")
  18. 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⟩])
  19. s ← 6.0

    pass 2 of 2
    71def 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.5
  20. total ← 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 ---
  21. 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'>)}")
  22. def name(self) -> str:

    pass 2 of 2
    79def name(self⟨Triangle C⟩) -> str:80    return "Triangle"
  23. 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: True
  24. print(" === 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.

abstractmethod Method without implementation. Subclass must provide the implementation.

Concrete methods in ABC

Mix abstract and regular methods.

concrete_methods.py
Replay: real traced execution (multi-file project)
# 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)
""")

  1. 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() #?createcsv
    output=== Abstract and Concrete Methods ===
  2. def __init__(self): #?jsoninit

    66def __init__(self⟨JSONProcessor A⟩): #?jsoninit67    super().__init__("JSONProcessor") #?jsoncallsuper68    self.storage = [] #?jsonstorage
  3. self.name ← JSONProcessor, self.processed_count ← 0

    pass 1 of 3
    11def __init__(self⟨JSONProcessor A⟩, nameJSONProcessor): #?processorinit12    self.name→ JSONProcessor = nameJSONProcessor13    self.processed_count→ 0 = 0 #?processedcount
    All 3 passes — pass 1 is the card above
    passselfnameself.nameself.processed_count
    1⟨JSONProcessor A⟩JSONProcessorJSONProcessor0
    2⟨CSVProcessor B⟩CSVProcessorCSVProcessor0
    3⟨TextProcessor C⟩TextProcessorTextProcessor0
  4. self.storage ← []

    67super().__init__("JSONProcessor") #?jsoncallsuper68self.storage→ [] = [] #?jsonstorage
  5. json_proc ← ⟨JSONProcessor A⟩

    128# Create processors #?createprocessors129json_proc→ ⟨JSONProcessor A⟩ = JSONProcessor() #?createjson130csv_proc = CSVProcessor() #?createcsv131text_proc = TextProcessor() #?createtext
  6. def __init__(self): #?csvinit

    89def __init__(self⟨CSVProcessor B⟩): #?csvinit90    super().__init__("CSVProcessor")91    self.storage = []
  7. self.storage ← []

    90super().__init__("CSVProcessor")91self.storage→ [] = []
  8. csv_proc ← ⟨CSVProcessor B⟩

    129json_proc = JSONProcessor() #?createjson130csv_proc→ ⟨CSVProcessor B⟩ = CSVProcessor() #?createcsv131text_proc = TextProcessor() #?createtext
  9. def __init__(self): #?textinit

    109def __init__(self⟨TextProcessor C⟩): #?textinit110    super().__init__("TextProcessor")111    self.storage = []
  10. self.storage ← []

    110super().__init__("TextProcessor")111self.storage→ [] = []
  11. 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 ---
  12. def process(self, data): #?processmethod

    pass 1 of 4
    31# 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...") #?processstep1
    output[JSONProcessor] Starting processing...
    All 4 passes — pass 1 is the card above
    passselfdataself.name
    1⟨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
  13. def validate(self, data) -> bool: #?jsonvalidate

    pass 1 of 2
    70def 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 #?jsonvalidatelogic
  14. print(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 passed
  15. transformed ← {'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'}
  16. 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 complete
  17. self.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'}) #?appendstorage
    output[JSONProcessor] Data saved
  18. result ← {'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 ---
  19. 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 #?csvvalidatelogic
  20. print(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 passed
  21. def 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 ']] #?csvtransformlogic
  22. result ← ['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 complete
  23. self.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'])) #?csvjoin
    output[CSVProcessor] Data saved
  24. result ← ['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 ---
  25. 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()) > 0
  26. print(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 passed
  27. def 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() #?texttransformlogic
  28. result ← 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 complete
  29. self.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 saved
  30. result ← 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 ---
  31. def validate(self, data) -> bool: #?jsonvalidate

    pass 2 of 2
    70def 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 #?jsonvalidatelogic
  32. if 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!
  33. 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()) #?csvstats
    outputResult: None
    
    --- Stats (Concrete Method) ---
  34. def get_stats(self): #?getstats

    pass 1 of 3
    58def 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
    passselfself.name
    1⟨JSONProcessor A⟩JSONProcessor
    2⟨CSVProcessor B⟩CSVProcessor
    3⟨TextProcessor C⟩TextProcessor
  35. 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()) #?textstats
    outputJSONProcessor: processed 1 items
  36. print(csv_proc.get_stats()) #?csvstats

    163print(json_proc.get_stats()) #?jsonstats164print(csv_proc⟨CSVProcessor B⟩.get_stats()) #?csvstats165print(text_proc⟨TextProcessor C⟩.get_stats()) #?textstats
    outputCSVProcessor: processed 1 items
  37. print(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.py
Replay: real traced execution (multi-file project)
# 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
""")

  1. 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") #?createmotorcycle
    output=== Abstract Properties ===
    --- Vehicles ---
  2. self._brand ← Toyota

    pass 1 of 3
    11def __init__(self⟨Car A⟩, brandToyota): #?vehicleinit12    self._brand→ Toyota = brandToyota #?privatebrand
    All 3 passes — pass 1 is the card above
    passselfbrandself._brand
    1⟨Car A⟩ToyotaToyota
    2⟨Motorcycle B⟩HarleyHarley
    3⟨Truck C⟩VolvoVolvo
  3. car ← ⟨Car A⟩

    141print("--- Vehicles ---")142car→ ⟨Car A⟩ = Car("Toyota") #?createcar143motorcycle = Motorcycle("Harley") #?createmotorcycle144truck = Truck("Volvo", 3) #?createtruck
  4. motorcycle ← ⟨Motorcycle B⟩

    142car = Car("Toyota") #?createcar143motorcycle→ ⟨Motorcycle B⟩ = Motorcycle("Harley") #?createmotorcycle144truck = Truck("Volvo", 3) #?createtruck
  5. def __init__(self, brand, axles): #?truckinit

    65def __init__(self⟨Truck C⟩, brandVolvo, axles3): #?truckinit66    super().__init__(brand) #?truckcallsuper67    self._axles = axles #?axlesattr
  6. self._axles ← 3

    66super().__init__(brand) #?truckcallsuper67self._axles→ 3 = axles3 #?axlesattr
  7. truck ← ⟨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⟩] #?vehicleslist
  8. for v in vehicles: #?iteratevehicles

    pass 1 of 3
    148for 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}") #?accesswheels
    All 3 passes — pass 1 is the card above
    passvselfself._axles
    1⟨Car A⟩⟨Car A⟩
    2⟨Motorcycle B⟩⟨Motorcycle B⟩
    3⟨Truck C⟩⟨Truck C⟩3
  9. def describe(self) -> str: #?describemethod

    pass 1 of 3
    33# 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
    passselfself.brandself.vehicle_typeself.wheelsself._axles
    1⟨Car A⟩ToyotaCar4
    2⟨Motorcycle B⟩HarleyMotorcycle2
    3⟨Truck C⟩VolvoTruck (3-axle)63
  10. def brand(self) -> str: #?brandproperty

    pass 1 of 3
    28@property29def brand(self⟨Car A⟩) -> str: #?brandproperty30    """Brand is concrete - same logic for all."""31    return self._brandToyota
    All 3 passes — pass 1 is the card above
    passselfself._brandself._axles
    1⟨Car A⟩Toyota
    2⟨Motorcycle B⟩Harley
    3⟨Truck C⟩Volvo3
  11. def vehicle_type(self) -> str: #?carvehicletype

    45@property46def vehicle_type(self⟨Car A⟩) -> str: #?carvehicletype47    return "Car"
  12. def wheels(self) -> int: #?carwheels

    pass 1 of 2
    41@property42def wheels(self⟨Car A⟩) -> int: #?carwheels43    return 4 #?return4
  13. print(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 wheels
  14. def wheels(self) -> int: #?carwheels

    pass 2 of 2
    41@property42def wheels(self⟨Car A⟩) -> int: #?carwheels43    return 4 #?return4
  15. print(f" wheels property: {v.wheels}") #?accesswheels

    149print(f"{v.describe()}") #?calldescribe150print(f"  wheels property: {v.wheels4}") #?accesswheels151print()
    output  wheels property: 4
  16. def vehicle_type(self) -> str: #?motorcyclevehicletype

    57@property58def vehicle_type(self⟨Motorcycle B⟩) -> str: #?motorcyclevehicletype59    return "Motorcycle"
  17. def wheels(self) -> int: #?motorcyclewheels

    pass 1 of 2
    53@property54def wheels(self⟨Motorcycle B⟩) -> int: #?motorcyclewheels55    return 2 #?return2
  18. print(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 wheels
  19. def wheels(self) -> int: #?motorcyclewheels

    pass 2 of 2
    53@property54def wheels(self⟨Motorcycle B⟩) -> int: #?motorcyclewheels55    return 2 #?return2
  20. print(f" wheels property: {v.wheels}") #?accesswheels

    149print(f"{v.describe()}") #?calldescribe150print(f"  wheels property: {v.wheels2}") #?accesswheels151print()
    output  wheels property: 2
  21. def vehicle_type(self) -> str: #?truckvehicletype

    73@property74def vehicle_type(self⟨Truck C⟩) -> str: #?truckvehicletype75    return f"Truck ({self._axles3}-axle)"
  22. def wheels(self) -> int: #?truckwheels

    pass 1 of 2
    69@property70def wheels(self⟨Truck C⟩) -> int: #?truckwheels71    return self._axles3 * 2 #?calculatewheels
  23. print(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 wheels
  24. def wheels(self) -> int: #?truckwheels

    pass 2 of 2
    69@property70def wheels(self⟨Truck C⟩) -> int: #?truckwheels71    return self._axles3 * 2 #?calculatewheels
  25. print(f" wheels property: {v.wheels}") #?accesswheels

    149print(f"{v.describe()}") #?calldescribe150print(f"  wheels property: {v.wheels6}") #?accesswheels151print()
    output  wheels property: 6
  26. print("--- Accounts ---")

    153# Account examples #?accountdemo154print("--- Accounts ---")155156savings = SavingsAccount("Alice") #?createsavings157savings.balance = 500.0 #?setsavingsbalance
    output--- Accounts ---
  27. self._owner ← Alice, self._balance ← 0.0

    pass 1 of 2
    84def __init__(self⟨SavingsAccount D⟩, ownerAlice): #?accountinit85    self._owner→ Alice = ownerAlice86    self._balance→ 0.0 = 0.0 #?balanceattr
  28. savings ← ⟨SavingsAccount D⟩

    156savings→ ⟨SavingsAccount D⟩ = SavingsAccount("Alice") #?createsavings157savings.balance = 500.0 #?setsavingsbalance158print(f"Savings: {savings.owner}, balance=${savings.balance}")
  29. self._balance ← 500.0

    pass 1 of 2
    115@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 #?setbalance
  30. savings.balance ← 500.0

    156savings = SavingsAccount("Alice") #?createsavings157savings.balance→ 500.0 = 500.0 #?setsavingsbalance158print(f"Savings: {savings.ownerAlice}, balance=${savings.balance500.0}")
  31. def owner(self) -> str: #?ownerproperty

    pass 1 of 2
    88@property89def owner(self⟨SavingsAccount D⟩) -> str: #?ownerproperty90    return self._ownerAlice
  32. def balance(self) -> float: #?savingsgetter

    111@property112def balance(self⟨SavingsAccount D⟩) -> float: #?savingsgetter113    return self._balance500.0
  33. print(f"Savings: {savings.owner}, balance=${savings.balance}")

    157savings.balance = 500.0 #?setsavingsbalance158print(f"Savings: {savings.ownerAlice}, balance=${savings.balance500.0}")
    outputSavings: Alice, balance=$500.0
  34. def balance(self, value: float): #?savingssetter

    pass 2 of 2
    115@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}")
  35. 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 #?setbalance
  36. except 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.0
  37. print()

    166print()167168checking = CheckingAccount("Bob") #?createchecking169checking.balance = 100.0 #?setcheckingbalance
  38. self._owner ← Bob, self._balance ← 0.0

    pass 2 of 2
    84def __init__(self⟨CheckingAccount E⟩, ownerBob): #?accountinit85    self._owner→ Bob = ownerBob86    self._balance→ 0.0 = 0.0 #?balanceattr
  39. checking ← ⟨CheckingAccount E⟩

    168checking→ ⟨CheckingAccount E⟩ = CheckingAccount("Bob") #?createchecking169checking.balance = 100.0 #?setcheckingbalance170print(f"Checking: {checking.owner}, balance=${checking.balance}")
  40. self._balance ← 100.0

    pass 1 of 3
    131@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.0
    All 3 passes — pass 1 is the card above
    passvalueself.OVERDRAFT_LIMITeself._balance
    1100.0100.0
    2-300.0-300.0
    3-600.0-500.0Exceeded overdraft limit of $500.0
  41. checking.balance ← 100.0

    168checking = CheckingAccount("Bob") #?createchecking169checking.balance→ 100.0 = 100.0 #?setcheckingbalance170print(f"Checking: {checking.ownerBob}, balance=${checking.balance100.0}")
  42. def owner(self) -> str: #?ownerproperty

    pass 2 of 2
    88@property89def owner(self⟨CheckingAccount E⟩) -> str: #?ownerproperty90    return self._ownerBob
  43. def balance(self) -> float: #?checkinggetter

    pass 1 of 2
    127@property128def balance(self⟨CheckingAccount E⟩) -> float: #?checkinggetter129    return self._balance100.0
  44. print(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.0
  45. checking.balance ← -300.0

    172# Use overdraft #?useoverdraft173checking.balance→ -300.0 = -300.0 #?setnegative174print(f"After overdraft: balance=${checking.balance-300.0}")
  46. def balance(self) -> float: #?checkinggetter

    pass 2 of 2
    127@property128def balance(self⟨CheckingAccount E⟩) -> float: #?checkinggetter129    return self._balance-300.0
  47. print(f"After overdraft: balance=${checking.balance}")

    173checking.balance = -300.0 #?setnegative174print(f"After overdraft: balance=${checking.balance-300.0}")
    outputAfter overdraft: balance=$-300.0
  48. if 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 = value
  49. except 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.0
  50. print(" === 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.

multiple_inheritance.py
Replay: real traced execution (multi-file project)
# 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
""")

  1. """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 ---
  2. self.name ← Laptop, self.price ← 999.99, self.quantity ← 10

    pass 1 of 3
    67def __init__(self⟨Product A⟩, nameLaptop, price999.99, quantity10): #?productinit68    self.name→ Laptop = nameLaptop69    self.price→ 999.99 = price999.9970    self.quantity→ 10 = quantity10
    All 3 passes — pass 1 is the card above
    passselfnamepricequantityself.nameself.priceself.quantity
    1⟨Product A⟩Laptop999.9910Laptop999.9910
    2⟨Product B⟩Mouse29.9950Mouse29.9950
    3⟨Product C⟩Keyboard79.9930Keyboard79.9930
  3. 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: #?iterateproducts
    outputUsing Printable.print():
  4. for p in products: #?iterateproducts

    pass 1 of 3
    125print("Using Printable.print():")126for p⟨Product A⟩ in products[⟨Product A⟩, ⟨Product B⟩, ⟨Product C⟩]: #?iterateproducts127    p⟨Product A⟩.print() #?callprint
    All 3 passes — pass 1 is the card above
    passp
    1⟨Product A⟩
    2⟨Product B⟩
    3⟨Product C⟩
  5. def print(self): #?printconcrete

    pass 1 of 5
    14def 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
    passselfself.nameself.departmentself.salary
    1⟨Product A⟩
    2⟨Product B⟩
    3⟨Product C⟩
    4⟨Employee D⟩AliceEngineering95000
    5⟨Employee E⟩BobMarketing75000
  6. def to_string(self) -> str: #?productostring

    pass 1 of 6
    72# 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
    passselfself.nameself.priceself.quantity
    1⟨Product A⟩Laptop999.9910
    2⟨Product B⟩Mouse29.9950
    3⟨Product C⟩Keyboard79.9930
    4⟨Product B⟩Mouse29.9950
    5⟨Product C⟩Keyboard79.9930
    6⟨Product A⟩Laptop999.9910
  7. 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() #?callprint
    output[PRINT] Laptop: $999.99 (qty: 10)
  8. 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() #?callprint
    output[PRINT] Mouse: $29.99 (qty: 50)
  9. 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() #?callprint
    output[PRINT] Keyboard: $79.99 (qty: 30)
  10. print()

    129print()130131# Using Serializable interface #?useserializable132print("Using Serializable.to_json_string():")133for p in products:
    outputUsing Serializable.to_json_string():
  11. for p in products:

    pass 1 of 3
    132print("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()}") #?calltojson
    All 3 passes — pass 1 is the card above
    passp
    1⟨Product A⟩
    2⟨Product B⟩
    3⟨Product C⟩
  12. def to_json_string(self) -> str: #?tojsonconcrete

    pass 1 of 5
    27def 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()) #?dumpsjson
    All 5 passes — pass 1 is the card above
    passselfself.nameself.departmentself.salary
    1⟨Product A⟩
    2⟨Product B⟩
    3⟨Product C⟩
    4⟨Employee D⟩AliceEngineering95000
    5⟨Employee E⟩BobMarketing75000
  13. def to_dict(self) -> dict: #?producttodict

    pass 1 of 3
    76# 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
    passselfself.nameself.priceself.quantity
    1⟨Product A⟩Laptop999.9910
    2⟨Product B⟩Mouse29.9950
    3⟨Product C⟩Keyboard79.9930
  14. print(f" {p.to_json_string()}") #?calltojson

    133for p in products:134    print(f"  {p⟨Product A⟩.to_json_string()}") #?calltojson
    output  {"name": "Laptop", "price": 999.99, "quantity": 10}
  15. print(f" {p.to_json_string()}") #?calltojson

    133for p in products:134    print(f"  {p⟨Product B⟩.to_json_string()}") #?calltojson
    output  {"name": "Mouse", "price": 29.99, "quantity": 50}
  16. print(f" {p.to_json_string()}") #?calltojson

    133for p in products:134    print(f"  {p⟨Product C⟩.to_json_string()}") #?calltojson
    output  {"name": "Keyboard", "price": 79.99, "quantity": 30}
  17. 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: #?printsorted
    outputUsing Comparable (sorting by price):
  18. def __lt__(self, other): #?ltmethod

    pass 1 of 5
    44def __lt__(self⟨Product B⟩, other⟨Product A⟩): #?ltmethod45    return self.compare_to(other⟨Product A⟩) < 0
    All 5 passes — pass 1 is the card above
    passselfotherself.priceother.price
    1⟨Product B⟩⟨Product A⟩29.99999.99
    2⟨Product C⟩⟨Product B⟩
    3⟨Product C⟩⟨Product A⟩79.99999.99
    4⟨Product C⟩⟨Product B⟩
    5⟨Product A⟩⟨Product B⟩
  19. def compare_to(self, other) -> int: #?productcompareto # Compa…

    pass 1 of 7
    84# From Comparable #?fromcomparable85def compare_to(self⟨Product B⟩, other⟨Product A⟩) -> int: #?productcompareto86    # Compare by price #?comparebyprice87    if self.price < other.price:88        return -1
    All 7 passes — pass 1 is the card above
    passselfotherself.priceother.price
    1⟨Product B⟩⟨Product A⟩29.99999.99
    2⟨Product C⟩⟨Product B⟩
    3⟨Product C⟩⟨Product A⟩79.99999.99
    4⟨Product C⟩⟨Product B⟩
    5⟨Product A⟩⟨Product B⟩
    6⟨Product A⟩⟨Product B⟩
    7⟨Product A⟩⟨Product B⟩
  20. if self.price < other.price:

    pass 1 of 2
    86# Compare by price #?comparebyprice87if self.price29.99 < other.price999.99:88    return -189elif self.price > other.price:
  21. elif self.price > other.price:

    pass 1 of 5
    88    return -189elif self.price79.99 > other.price29.99:90    return 191return 0
    All 5 passes — pass 1 is the card above
    passself.price
    179.99
    279.99
    3999.99
    4999.99
    5999.99
  22. if self.price < other.price:

    pass 2 of 2
    86# Compare by price #?comparebyprice87if self.price79.99 < other.price999.99:88    return -189elif self.price > other.price:
  23. 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: #?printsorted
  24. for p in sorted_products: #?printsorted

    pass 1 of 3
    140sorted_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
    passp
    1⟨Product B⟩
    2⟨Product C⟩
    3⟨Product A⟩
  25. print(f" {p.to_string()}")

    141for p in sorted_products: #?printsorted142    print(f"  {p⟨Product B⟩.to_string()}")
    output  Mouse: $29.99 (qty: 50)
  26. print(f" {p.to_string()}")

    141for p in sorted_products: #?printsorted142    print(f"  {p⟨Product C⟩.to_string()}")
    output  Keyboard: $79.99 (qty: 30)
  27. print(f" {p.to_string()}")

    141for p in sorted_products: #?printsorted142    print(f"  {p⟨Product A⟩.to_string()}")
    output  Laptop: $999.99 (qty: 10)
  28. 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}") #?gtcompare
    outputComparing Laptop and Mouse:
  29. 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}") #?eqcompare
    output  Laptop < Mouse: False
  30. def __gt__(self, other): #?gtmethod

    50def __gt__(self⟨Product A⟩, other⟨Product B⟩): #?gtmethod51    return self.compare_to(other⟨Product B⟩) > 0
  31. print(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⟩}") #?eqcompare
    output  Laptop > Mouse: True
  32. def __eq__(self, other): #?eqmethod

    56def __eq__(self⟨Product A⟩, other⟨Product B⟩): #?eqmethod57    return self.compare_to(other⟨Product B⟩) == 0
  33. print(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 ---
  34. self.name ← Alice, self.department ← Engineering, self.salary ← 95000

    pass 1 of 2
    98def __init__(self⟨Employee D⟩, nameAlice, departmentEngineering, salary95000): #?employeeinit99    self.name→ Alice = nameAlice100    self.department→ Engineering = departmentEngineering101    self.salary→ 95000 = salary95000
  35. self.name ← Bob, self.department ← Marketing, self.salary ← 75000

    pass 2 of 2
    98def __init__(self⟨Employee E⟩, nameBob, departmentMarketing, salary75000): #?employeeinit99    self.name→ Bob = nameBob100    self.department→ Marketing = departmentMarketing101    self.salary→ 75000 = salary75000
  36. employees ← [⟨Employee D⟩, ⟨Employee E⟩]

    154print("\n--- Employees ---")155employees→ [⟨Employee D⟩, ⟨Employee E⟩] = [156    Employee("Alice", "Engineering", 95000),157    Employee("Bob", "Marketing", 75000),158]
  37. for emp in employees: #?iterateemployees

    pass 1 of 2
    160for emp⟨Employee D⟩ in employees[⟨Employee D⟩, ⟨Employee E⟩]: #?iterateemployees161    emp⟨Employee D⟩.print() #?empprint162    print(f"  JSON: {emp.to_json_string()}") #?empjson
  38. def to_string(self) -> str: #?employeetostring

    pass 1 of 2
    103def to_string(self⟨Employee D⟩) -> str: #?employeetostring104    return f"{self.nameAlice} ({self.departmentEngineering}) - ${self.salary95000:,.2f}"
  39. 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()}") #?empjson
    output[PRINT] Alice (Engineering) - $95,000.00
  40. def to_dict(self) -> dict: #?employeetodict

    pass 1 of 2
    106def to_dict(self⟨Employee D⟩) -> dict: #?employeetodict107    return {108        "name": self.nameAlice,109        "department": self.departmentEngineering,110        "salary": self.salary95000111    }
  41. print(f" JSON: {emp.to_json_string()}") #?empjson

    161emp.print() #?empprint162print(f"  JSON: {emp⟨Employee D⟩.to_json_string()}") #?empjson
    output  JSON: {"name": "Alice", "department": "Engineering", "salary": 95000}
  42. for emp in employees: #?iterateemployees

    pass 2 of 2
    160for emp⟨Employee E⟩ in employees[⟨Employee D⟩, ⟨Employee E⟩]: #?iterateemployees161    emp⟨Employee E⟩.print() #?empprint162    print(f"  JSON: {emp.to_json_string()}") #?empjson
  43. def to_string(self) -> str: #?employeetostring

    pass 2 of 2
    103def to_string(self⟨Employee E⟩) -> str: #?employeetostring104    return f"{self.nameBob} ({self.departmentMarketing}) - ${self.salary75000:,.2f}"
  44. 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()}") #?empjson
    output[PRINT] Bob (Marketing) - $75,000.00
  45. def to_dict(self) -> dict: #?employeetodict

    pass 2 of 2
    106def to_dict(self⟨Employee E⟩) -> dict: #?employeetodict107    return {108        "name": self.nameBob,109        "department": self.departmentMarketing,110        "salary": self.salary75000111    }
  46. print(f" JSON: {emp.to_json_string()}") #?empjson

    161emp.print() #?empprint162print(f"  JSON: {emp⟨Employee E⟩.to_json_string()}") #?empjson
    output  JSON: {"name": "Bob", "department": "Marketing", "salary": 75000}
  47. 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