AiTechWorlds
AiTechWorlds
Inheritance lets a class reuse another class's attributes and methods instead of rewriting them. It works like inheriting traits from a parent — a child gets the parent's features automatically, then adds or overrides its own. Polymorphism is the companion idea: different objects respond to the same method call in their own way. Together, they let you write code once and extend it many times.
# Base class (parent / superclass)
class Animal:
def __init__(self, name, age):
self.name = name
self.age = age
self.is_alive = True
def eat(self, food):
print(f"{self.name} eats {food}")
def sleep(self):
print(f"{self.name} sleeps")
def describe(self):
return f"{self.name}, age {self.age}"
def speak(self):
raise NotImplementedError("Subclasses must implement speak()")
# Child class (subclass) inherits from Animal
class Dog(Animal):
def __init__(self, name, age, breed):
super().__init__(name, age) # Call parent __init__
self.breed = breed
def speak(self):
return f"{self.name} says: Woof!"
def fetch(self, item):
return f"{self.name} fetches the {item}!"
def describe(self):
base = super().describe() # Call parent method
return f"{base} ({self.breed})"
class Cat(Animal):
def __init__(self, name, age, indoor=True):
super().__init__(name, age)
self.indoor = indoor
def speak(self):
return f"{self.name} says: Meow!"
def purr(self):
return f"{self.name} purrs..."
# Usage
dog = Dog("Rex", 3, "German Shepherd")
cat = Cat("Whiskers", 5)
print(dog.speak()) # Rex says: Woof!
print(cat.speak()) # Whiskers says: Meow!
print(dog.describe()) # Rex, age 3 (German Shepherd)
dog.eat("bones") # Rex eats bones (inherited from Animal)
print(dog.fetch("ball")) # Rex fetches the ball!
# isinstance checks inheritance chain
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True — Dog IS-A Animal
print(isinstance(dog, Cat)) # False
# issubclass
print(issubclass(Dog, Animal)) # True
print(issubclass(Cat, Animal)) # TruePolymorphism means you can treat different objects the same way through a shared interface.
animals = [
Dog("Rex", 3, "Lab"),
Cat("Luna", 2),
Dog("Max", 5, "Poodle"),
Cat("Oliver", 7, indoor=False)
]
# Each speaks differently, but we call the same method on all
for animal in animals:
print(animal.speak())
# Function that works with any Animal
def make_noise(animal: Animal):
"""Works with any subclass of Animal — polymorphism in action."""
return animal.speak()
# This works even with future Animal subclasses we haven't written yetUse ABC to declare that subclasses MUST implement certain methods.
from abc import ABC, abstractmethod
class Shape(ABC):
def __init__(self, color="black"):
self.color = color
@abstractmethod
def area(self) -> float:
"""Subclasses must implement this."""
pass
@abstractmethod
def perimeter(self) -> float:
pass
def describe(self):
# Concrete method — shared by all shapes
return (f"{self.color} {self.__class__.__name__}: "
f"area={self.area():.2f}, perimeter={self.perimeter():.2f}")
class Circle(Shape):
def __init__(self, radius, color="black"):
super().__init__(color)
self.radius = radius
def area(self):
import math
return math.pi * self.radius ** 2
def perimeter(self):
import math
return 2 * math.pi * self.radius
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.height
def perimeter(self):
return 2 * (self.width + self.height)
class Triangle(Shape):
def __init__(self, a, b, c, color="black"):
super().__init__(color)
self.a, self.b, self.c = a, b, c
def area(self):
s = self.perimeter() / 2 # Semi-perimeter
return (s * (s-self.a) * (s-self.b) * (s-self.c)) ** 0.5
def perimeter(self):
return self.a + self.b + self.c
# Can't instantiate abstract class
try:
s = Shape() # TypeError: Can't instantiate abstract class
except TypeError as e:
print(e)
# Works with concrete subclasses
shapes = [Circle(5, "red"), Rectangle(4, 6, "blue"), Triangle(3, 4, 5)]
for shape in shapes:
print(shape.describe())
# Total area — works with any Shape
total = sum(s.area() for s in shapes)
print(f"Total area: {total:.2f}")Python supports multiple inheritance — a class can inherit from multiple parents.
class Flyable:
def fly(self):
return f"{self.name} flies through the air!"
def altitude(self):
return "Up in the sky"
class Swimmable:
def swim(self):
return f"{self.name} swims in the water!"
def depth(self):
return "Under the surface"
class Duck(Animal, Flyable, Swimmable):
def speak(self):
return f"{self.name} says: Quack!"
duck = Duck("Donald", 3)
print(duck.speak()) # Donald says: Quack!
print(duck.fly()) # Donald flies through the air!
print(duck.swim()) # Donald swims in the water!
# Method Resolution Order (MRO) — Python's rules for which method to use
print(Duck.__mro__)
# (<class 'Duck'>, <class 'Animal'>, <class 'Flyable'>, <class 'Swimmable'>, <class 'object'>)Inheritance models "is-a" relationships. Composition models "has-a" relationships. Experienced developers often prefer composition.
# Inheritance approach: Car IS-A Vehicle
class Vehicle:
def __init__(self, make, model):
self.make = make
self.model = model
def start(self):
print("Engine started")
class Car(Vehicle):
def __init__(self, make, model, doors):
super().__init__(make, model)
self.doors = doors
# Composition approach: Car HAS-A Engine
class Engine:
def __init__(self, horsepower, fuel_type):
self.horsepower = horsepower
self.fuel_type = fuel_type
def start(self):
return "Engine started"
def describe(self):
return f"{self.horsepower}hp {self.fuel_type} engine"
class GPS:
def navigate(self, destination):
return f"Navigating to {destination}"
class Car:
def __init__(self, make, model, engine, has_gps=False):
self.make = make
self.model = model
self.engine = engine # HAS-A engine
self.gps = GPS() if has_gps else None
def start(self):
return self.engine.start() # Delegate to engine
def navigate(self, destination):
if self.gps is None:
raise ValueError("No GPS installed")
return self.gps.navigate(destination)
tesla = Car("Tesla", "Model S", Engine(670, "electric"), has_gps=True)
print(tesla.start())
print(tesla.navigate("San Francisco"))
print(tesla.engine.describe())When to use inheritance: The child truly IS-A parent (Dog IS-A Animal; Circle IS-A Shape)
When to use composition: The child HAS-A component (Car HAS-A Engine; User HAS-A Address)
Mixins add specific functionality to classes without deep inheritance hierarchies.
class JSONMixin:
"""Adds JSON serialization to any class."""
import json
def to_json(self):
import json
return json.dumps(self.__dict__, default=str)
@classmethod
def from_json(cls, json_string):
import json
data = json.loads(json_string)
return cls(**data)
class LogMixin:
"""Adds logging to any class."""
def log(self, message):
print(f"[{self.__class__.__name__}] {message}")
class Product(JSONMixin, LogMixin):
def __init__(self, name, price, category):
self.name = name
self.price = price
self.category = category
def apply_discount(self, pct):
old_price = self.price
self.price = round(self.price * (1 - pct/100), 2)
self.log(f"Discount applied: ${old_price} → ${self.price}")
laptop = Product("MacBook", 2499.99, "Electronics")
laptop.apply_discount(10)
json_str = laptop.to_json()
print(json_str)
restored = Product.from_json(json_str)
print(restored.name, restored.price)Mixins are a practical alternative to deeply nested inheritance trees — they keep responsibilities separated and classes composable.
Next lesson: Magic Methods & Operator Overloading — making your classes behave like built-ins.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises