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.
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:
Create an abstract Shape class with abstract methods area() and perimeter()
Implement Circle and Rectangle classes that inherit from Shape
Add appropriate attributes to each class (e.g., radius for Circle, width/height for Rectangle)
Implement the required methods for each class
Add a __str__ method to each class for better string representation
Test your implementation with different shapes and print their areas and perimeters