084. Basic Inheritance
Extend existing classes to add specialized behavior
084. Basic Inheritance
He adds GPU processes to the RAM manager. A GPU process is like a regular Process, but also has vram_mb (video RAM). He does NOT want to duplicate all of Process.
Inheritance: GpuProcess extends Process and adds what it needs.
class Process:
def __init__(self, name, pid, rss_mb):
self.name = name
self.pid = pid
self.rss_mb = rss_mb
def total_memory(self):
return self.rss_mb
def summary(self):
return f"{self.name}[{self.pid}]: {self.rss_mb} MB RAM"
class GpuProcess(Process):
def __init__(self, name, pid, rss_mb, vram_mb):
super().__init__(name, pid, rss_mb) # initialize the parent
self.vram_mb = vram_mb # add GPU-specific attribute
def total_memory(self): # override the parent method
return self.rss_mb + self.vram_mb
gpu = GpuProcess("cuda_worker", 5678, 200, 4096)
print(gpu.total_memory()) # 4296
print(isinstance(gpu, Process)) # True — a GpuProcess IS-A ProcessWithout super().__init__(), self.name, self.pid, self.rss_mb are never set and every method call crashes.
💡 Fun fact: Python supports multiple inheritance — a class can inherit from more than one parent simultaneously. To handle the diamond problem (where two parent classes share a grandparent), Python uses the C3 linearization algorithm to determine a consistent Method Resolution Order (MRO). You can inspect it with ClassName.__mro__. Most languages (Java, C#) deliberately forbid multiple inheritance to avoid this complexity.
⚠️ Watch out: Forgetting super().__init__() in a subclass is the single most common inheritance bug. The child’s __init__ completely replaces the parent’s — so if you don’t explicitly call super().__init__(), none of the parent’s attributes get set, and every method that touches them will crash with AttributeError.
🤔 Think about it: Inheritance models an “is-a” relationship: a GpuProcess is-a Process. But Python also supports “has-a” composition — giving GpuProcess a Process object as an attribute instead of inheriting from it. When would you choose composition over inheritance, and what makes one approach cleaner than the other?
Learning objectives
- Create subclasses that inherit from a base class
- Call super().init() to initialize the parent
- Override methods to provide specialized behavior
Key concepts
- inheritance
- super()
- method overriding
- subclass
- isinstance
Try it
Concept detail
Inheritance: class Child(Parent): — Child gets all attributes and methods of Parent.
class Circle(Shape): # Circle IS-A Shape
...super().init(…) calls the parent’s init. ALWAYS call this in a child’s init when the parent has its own init logic — otherwise parent attributes are never set.
class Circle(Shape):
def __init__(self, radius, color="black"):
super().__init__(color) # sets self.color via Shape.__init__
self.radius = radius # then add child-specific attributesOverriding: redefine a method in the child to change its behavior.
class Circle(Shape):
def area(self): # overrides Shape.area()
return math.pi * self.radius ** 2The child inherits everything it does NOT override: c = Circle(5) str(c) # uses Shape.str — not overridden in Circle
isinstance(obj, Parent): isinstance(Circle(1), Shape) # True — Circle IS-A Shape isinstance(Circle(1), Circle) # True isinstance(Shape(), Circle) # False — Shape is NOT a Circle
Use inheritance for “is-a” relationships: Circle IS-A Shape ✓ GpuProcess IS-A Process ✓ TaskList IS-A BankAccount ✗ (no relationship)
Solution
import math
class Shape:
def __init__(self, color="black"):
self.color = color
def area(self):
return 0
def __str__(self):
return f"{self.__class__.__name__}(color={self.color}, area={self.area():.2f})"
class Circle(Shape):
def __init__(self, radius, color="black"):
super().__init__(color)
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
class Rectangle(Shape):
def __init__(self, width, height, color="black"):
super().__init__(color)
self.width = width
self.height = height
def area(self):
return self.width * self.heightTests
def test_circle_area_unit():
c = Circle(1)
assert abs(c.area() - math.pi) < 0.0001
def test_circle_area_5():
c = Circle(5)
assert abs(c.area() - math.pi * 25) < 0.0001
def test_circle_color_default():
c = Circle(3)
assert c.color == "black"
def test_circle_color_custom():
c = Circle(3, color="red")
assert c.color == "red"
def test_rectangle_area():
r = Rectangle(4, 5)
assert r.area() == 20
def test_rectangle_area_square():
r = Rectangle(3, 3)
assert r.area() == 9
def test_rectangle_color():
r = Rectangle(3, 3, "blue")
assert r.color == "blue"
def test_isinstance():
c = Circle(1)
assert isinstance(c, Shape)
assert isinstance(c, Circle)