🧬 Unit-V: Advanced OOP in Python

Lecture 3: Inheritance, Abstract Classes & Garbage Collection

1. Inheritance in Python

Inheritance allows us to define a class that inherits all the methods and properties from another class.

1.1 Basic Inheritance

# Parent/Base class class Animal: def __init__(self, name): self.name = name def speak(self): return f"{self.name} makes a sound" # Child/Derived class class Dog(Animal): def speak(self): return f"{self.name} says Woof!" def wag_tail(self): return f"{self.name} is wagging its tail" # Create instances animal = Animal("Generic Animal") dog = Dog("Buddy") print(animal.speak()) # Generic Animal makes a sound print(dog.speak()) # Buddy says Woof! print(dog.wag_tail()) # Buddy is wagging its tail

1.2 The super() Function

class Cat(Animal): def __init__(self, name, color): super().__init__(name) # Call parent's __init__ self.color = color def speak(self): return f"{self.name} (a {self.color} cat) says Meow!" cat = Cat("Whiskers", "black") print(cat.speak()) # Whiskers (a black cat) says Meow!
Note: Always use super() to call parent class methods rather than hardcoding the parent class name. This makes your code more maintainable, especially with multiple inheritance.
2. Types of Inheritance
Single
Multi-level
Hierarchical
Multiple
Single Inheritance

A class inherits from a single parent class.

class Parent: def parent_method(self): return "Parent method" class Child(Parent): def child_method(self): return "Child method" child = Child() print(child.parent_method()) # Parent method print(child.child_method()) # Child method
Multi-level Inheritance

A class inherits from a child class.

class GrandParent: def grandparent_method(self): return "Grandparent method" class Parent(GrandParent): def parent_method(self): return "Parent method" class Child(Parent): def child_method(self): return "Child method" child = Child() print(child.grandparent_method()) # Grandparent method print(child.parent_method()) # Parent method print(child.child_method()) # Child method
Hierarchical Inheritance

Multiple classes inherit from a single parent class.

class Animal: def __init__(self, name): self.name = name class Dog(Animal): def speak(self): return f"{self.name} says Woof!" class Cat(Animal): def speak(self): return f"{self.name} says Meow!" dog = Dog("Buddy") cat = Cat("Whiskers") print(dog.speak()) # Buddy says Woof! print(cat.speak()) # Whiskers says Meow!
Multiple Inheritance

A class inherits from multiple parent classes.

class Flyable: def fly(self): return f"{self.name} is flying" class Swimmable: def swim(self): return f"{self.name} is swimming" class Duck: def __init__(self, name): self.name = name def quack(self): return f"{self.name} says Quack!" class FlyingDuck(Duck, Flyable, Swimmable): pass donald = FlyingDuck("Donald") print(donald.quack()) # Donald says Quack! print(donald.fly()) # Donald is flying print(donald.swim()) # Donald is swimming
Method Resolution Order (MRO): Python uses the C3 linearization algorithm to determine the order in which to search for methods in the inheritance hierarchy. You can check the MRO using ClassName.__mro__ or ClassName.mro().
3. Abstract Base Classes (ABC)

Abstract classes are classes that contain one or more abstract methods. An abstract method is a method that is declared but contains no implementation.

from abc import ABC, abstractmethod class Shape(ABC): @abstractmethod def area(self): pass @abstractmethod def perimeter(self): pass # Can have concrete methods too def description(self): return "This is a shape" class Rectangle(Shape): def __init__(self, width, height): self.width = width self.height = height def area(self): return self.width * self.height def perimeter(self): return 2 * (self.width + self.height) # Override the concrete method def description(self): return f"Rectangle with width {self.width} and height {self.height}" # This will raise an error because abstract methods aren't implemented # shape = Shape() # TypeError: Can't instantiate abstract class Shape rect = Rectangle(4, 5) print(f"Area: {rect.area()}") # Area: 20 print(f"Perimeter: {rect.perimeter()}") # Perimeter: 18 print(rect.description()) # Rectangle with width 4 and height 5
Key Points about ABCs:
  • Abstract classes cannot be instantiated directly
  • Subclasses must implement all abstract methods
  • Abstract classes can have concrete methods with implementation
  • Useful for defining a common interface for subclasses
4. Garbage Collection in Python

Python's garbage collector automatically frees memory when objects are no longer in use.

4.1 Reference Counting

Python uses reference counting to track the number of references to an object.

import sys a = [] print(sys.getrefcount(a)) # 2 (one for 'a' and one for the argument) b = a print(sys.getrefcount(a)) # 3 (now referenced by 'b' too) del b print(sys.getrefcount(a)) # Back to 2 # When reference count reaches 0, the object is deleted del a # Reference count drops to 0, object is garbage collected

4.2 Circular References

Reference counting alone can't handle circular references. Python's garbage collector can detect and clean these up.

import gc class Node: def __init__(self, name): self.name = name self.next = None def __del__(self): print(f"Deleting {self.name}") # Create circular reference node1 = Node("Node 1") node2 = Node("Node 2") node1.next = node2 node2.next = node1 # Circular reference # Delete references del node1 del node2 # Force garbage collection print("Collecting garbage...") gc.collect() # Will detect and clean up the circular reference print("Garbage collection complete")

4.3 __del__ Method

The __del__ method is called when an object is about to be destroyed.

class Resource: def __init__(self, name): self.name = name print(f"Resource {self.name} created") def __del__(self): # Not guaranteed to be called in all cases print(f"Resource {self.name} is being deleted") def use(self): print(f"Using resource {self.name}") def process(): res = Resource("Temporary") res.use() # res is automatically deleted when the function ends process() # Output: # Resource Temporary created # Using resource Temporary # Resource Temporary is being deleted
Important: The __del__ method is not guaranteed to be called in all cases. For cleanup, use context managers (with statement) or the try/finally pattern instead.
5. Practice Exercise

Task: Create a Shape Hierarchy

Implement a hierarchy of shape classes using abstract base classes and inheritance.

from abc import ABC, abstractmethod import math # Your implementation here class Shape(ABC): @abstractmethod def area(self): pass @abstractmethod def perimeter(self): pass # Implement Circle and Rectangle classes that inherit from Shape # Test your implementation if __name__ == "__main__": # shapes = [Circle(5), Rectangle(4, 5)] # for shape in shapes: # print(f"{shape.__class__.__name__} - Area: {shape.area()}, Perimeter: {shape.perimeter()}") pass
Requirements:
  1. Create an abstract Shape class with abstract methods area() and perimeter()
  2. Implement Circle and Rectangle classes that inherit from Shape
  3. Add appropriate attributes to each class (e.g., radius for Circle, width/height for Rectangle)
  4. Implement the required methods for each class
  5. Add a __str__ method to each class for better string representation
  6. Test your implementation with different shapes and print their areas and perimeters