OOP Intermediate
Inheritance
Extending Classes
You have Employee with name and salary. Manager needs the same plus a team list. Instead of duplicating code, Manager extends Employee - inheriting its attributes and methods while adding its own.
Basic inheritance
Create a subclass that extends a parent.
# Basic Inheritance
class Animal:
"""Base class for all animals."""
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
def describe(self):
return f"I am {self.name}"
# Dog inherits from Animal
class Dog(Animal):
"""Dog class inherits from Animal."""
pass # No additional code - inherits everything
# Cat inherits from Animal
class Cat(Animal):
"""Cat class inherits from Animal."""
pass
# Bird with additional attribute
class Bird(Animal):
def __init__(self, name, can_fly=True):
# Call parent's __init__
super().__init__(name)
self.can_fly = can_fly
# Demonstrate basic inheritance
print("=== Basic Inheritance ===\n")
# Create Dog - uses inherited __init__
dog =
print(f"Dog name: {dog.name}")
print(f"Dog speaks: {dog.speak()}")
print(f"Dog describes: {dog.describe()}")
print()
# Create Cat - also inherits everything
cat = Cat("Whiskers")
print(f"Cat name: {cat.name}")
print(f"Cat speaks: {cat.speak()}")
print(f"Cat describes: {cat.describe()}")
print()
# Create Bird - has additional attribute
bird =
print(f"Bird name: {bird.name}")
print(f"Bird can fly: {bird.can_fly}")
print(f"Bird speaks: {bird.speak()}")
print()
# Bird that can't fly
penguin = Bird("Pingu", can_fly=False)
print(f"Penguin name: {penguin.name}")
print(f"Penguin can fly: {penguin.can_fly}")
print("\n=== Inheritance Rules ===")
print("""
1. Child inherits all attributes and methods
2. Syntax: class Child(Parent):
3. super().__init__() calls parent's __init__
4. Child can add new attributes/methods
5. 'pass' means no additions (pure inheritance)
""")
# Basic Inheritance
class Animal:
"""Base class for all animals."""
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
def describe(self):
return f"I am {self.name}"
# Dog inherits from Animal
class Dog(Animal):
"""Dog class inherits from Animal."""
pass # No additional code - inherits everything
# Cat inherits from Animal
class Cat(Animal):
"""Cat class inherits from Animal."""
pass
# Bird with additional attribute
class Bird(Animal):
def __init__(self, name, can_fly=True):
# Call parent's __init__
super().__init__(name)
self.can_fly = can_fly
# Demonstrate basic inheritance
print("=== Basic Inheritance ===\n")
# Create Dog - uses inherited __init__
dog =
print(f"Dog name: {dog.name}")
print(f"Dog speaks: {dog.speak()}")
print(f"Dog describes: {dog.describe()}")
print()
# Create Cat - also inherits everything
cat = Cat("Whiskers")
print(f"Cat name: {cat.name}")
print(f"Cat speaks: {cat.speak()}")
print(f"Cat describes: {cat.describe()}")
print()
# Create Bird - has additional attribute
bird =
print(f"Bird name: {bird.name}")
print(f"Bird can fly: {bird.can_fly}")
print(f"Bird speaks: {bird.speak()}")
print()
# Bird that can't fly
penguin = Bird("Pingu", can_fly=False)
print(f"Penguin name: {penguin.name}")
print(f"Penguin can fly: {penguin.can_fly}")
print("\n=== Inheritance Rules ===")
print("""
1. Child inherits all attributes and methods
2. Syntax: class Child(Parent):
3. super().__init__() calls parent's __init__
4. Child can add new attributes/methods
5. 'pass' means no additions (pure inheritance)
""")
# Basic Inheritance
class Animal:
"""Base class for all animals."""
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
def describe(self):
return f"I am {self.name}"
# Dog inherits from Animal
class Dog(Animal):
"""Dog class inherits from Animal."""
pass # No additional code - inherits everything
# Cat inherits from Animal
class Cat(Animal):
"""Cat class inherits from Animal."""
pass
# Bird with additional attribute
class Bird(Animal):
def __init__(self, name, can_fly=True):
# Call parent's __init__
super().__init__(name)
self.can_fly = can_fly
# Demonstrate basic inheritance
print("=== Basic Inheritance ===\n")
# Create Dog - uses inherited __init__
dog =
print(f"Dog name: {dog.name}")
print(f"Dog speaks: {dog.speak()}")
print(f"Dog describes: {dog.describe()}")
print()
# Create Cat - also inherits everything
cat = Cat("Whiskers")
print(f"Cat name: {cat.name}")
print(f"Cat speaks: {cat.speak()}")
print(f"Cat describes: {cat.describe()}")
print()
# Create Bird - has additional attribute
bird =
print(f"Bird name: {bird.name}")
print(f"Bird can fly: {bird.can_fly}")
print(f"Bird speaks: {bird.speak()}")
print()
# Bird that can't fly
penguin = Bird("Pingu", can_fly=False)
print(f"Penguin name: {penguin.name}")
print(f"Penguin can fly: {penguin.can_fly}")
print("\n=== Inheritance Rules ===")
print("""
1. Child inherits all attributes and methods
2. Syntax: class Child(Parent):
3. super().__init__() calls parent's __init__
4. Child can add new attributes/methods
5. 'pass' means no additions (pure inheritance)
""")
# Basic Inheritance
class Animal:
"""Base class for all animals."""
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
def describe(self):
return f"I am {self.name}"
# Dog inherits from Animal
class Dog(Animal):
"""Dog class inherits from Animal."""
pass # No additional code - inherits everything
# Cat inherits from Animal
class Cat(Animal):
"""Cat class inherits from Animal."""
pass
# Bird with additional attribute
class Bird(Animal):
def __init__(self, name, can_fly=True):
# Call parent's __init__
super().__init__(name)
self.can_fly = can_fly
# Demonstrate basic inheritance
print("=== Basic Inheritance ===\n")
# Create Dog - uses inherited __init__
dog =
print(f"Dog name: {dog.name}")
print(f"Dog speaks: {dog.speak()}")
print(f"Dog describes: {dog.describe()}")
print()
# Create Cat - also inherits everything
cat = Cat("Whiskers")
print(f"Cat name: {cat.name}")
print(f"Cat speaks: {cat.speak()}")
print(f"Cat describes: {cat.describe()}")
print()
# Create Bird - has additional attribute
bird =
print(f"Bird name: {bird.name}")
print(f"Bird can fly: {bird.can_fly}")
print(f"Bird speaks: {bird.speak()}")
print()
# Bird that can't fly
penguin = Bird("Pingu", can_fly=False)
print(f"Penguin name: {penguin.name}")
print(f"Penguin can fly: {penguin.can_fly}")
print("\n=== Inheritance Rules ===")
print("""
1. Child inherits all attributes and methods
2. Syntax: class Child(Parent):
3. super().__init__() calls parent's __init__
4. Child can add new attributes/methods
5. 'pass' means no additions (pure inheritance)
""")
# Basic Inheritance
class Animal:
"""Base class for all animals."""
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
def describe(self):
return f"I am {self.name}"
# Dog inherits from Animal
class Dog(Animal):
"""Dog class inherits from Animal."""
pass # No additional code - inherits everything
# Cat inherits from Animal
class Cat(Animal):
"""Cat class inherits from Animal."""
pass
# Bird with additional attribute
class Bird(Animal):
def __init__(self, name, can_fly=True):
# Call parent's __init__
super().__init__(name)
self.can_fly = can_fly
# Demonstrate basic inheritance
print("=== Basic Inheritance ===\n")
# Create Dog - uses inherited __init__
dog =
print(f"Dog name: {dog.name}")
print(f"Dog speaks: {dog.speak()}")
print(f"Dog describes: {dog.describe()}")
print()
# Create Cat - also inherits everything
cat = Cat("Whiskers")
print(f"Cat name: {cat.name}")
print(f"Cat speaks: {cat.speak()}")
print(f"Cat describes: {cat.describe()}")
print()
# Create Bird - has additional attribute
bird =
print(f"Bird name: {bird.name}")
print(f"Bird can fly: {bird.can_fly}")
print(f"Bird speaks: {bird.speak()}")
print()
# Bird that can't fly
penguin = Bird("Pingu", can_fly=False)
print(f"Penguin name: {penguin.name}")
print(f"Penguin can fly: {penguin.can_fly}")
print("\n=== Inheritance Rules ===")
print("""
1. Child inherits all attributes and methods
2. Syntax: class Child(Parent):
3. super().__init__() calls parent's __init__
4. Child can add new attributes/methods
5. 'pass' means no additions (pure inheritance)
""")
# Basic Inheritance
class Animal:
"""Base class for all animals."""
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
def describe(self):
return f"I am {self.name}"
# Dog inherits from Animal
class Dog(Animal):
"""Dog class inherits from Animal."""
pass # No additional code - inherits everything
# Cat inherits from Animal
class Cat(Animal):
"""Cat class inherits from Animal."""
pass
# Bird with additional attribute
class Bird(Animal):
def __init__(self, name, can_fly=True):
# Call parent's __init__
super().__init__(name)
self.can_fly = can_fly
# Demonstrate basic inheritance
print("=== Basic Inheritance ===\n")
# Create Dog - uses inherited __init__
dog =
print(f"Dog name: {dog.name}")
print(f"Dog speaks: {dog.speak()}")
print(f"Dog describes: {dog.describe()}")
print()
# Create Cat - also inherits everything
cat = Cat("Whiskers")
print(f"Cat name: {cat.name}")
print(f"Cat speaks: {cat.speak()}")
print(f"Cat describes: {cat.describe()}")
print()
# Create Bird - has additional attribute
bird =
print(f"Bird name: {bird.name}")
print(f"Bird can fly: {bird.can_fly}")
print(f"Bird speaks: {bird.speak()}")
print()
# Bird that can't fly
penguin = Bird("Pingu", can_fly=False)
print(f"Penguin name: {penguin.name}")
print(f"Penguin can fly: {penguin.can_fly}")
print("\n=== Inheritance Rules ===")
print("""
1. Child inherits all attributes and methods
2. Syntax: class Child(Parent):
3. super().__init__() calls parent's __init__
4. Child can add new attributes/methods
5. 'pass' means no additions (pure inheritance)
""")
# Basic Inheritance
class Animal:
"""Base class for all animals."""
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
def describe(self):
return f"I am {self.name}"
# Dog inherits from Animal
class Dog(Animal):
"""Dog class inherits from Animal."""
pass # No additional code - inherits everything
# Cat inherits from Animal
class Cat(Animal):
"""Cat class inherits from Animal."""
pass
# Bird with additional attribute
class Bird(Animal):
def __init__(self, name, can_fly=True):
# Call parent's __init__
super().__init__(name)
self.can_fly = can_fly
# Demonstrate basic inheritance
print("=== Basic Inheritance ===\n")
# Create Dog - uses inherited __init__
dog =
print(f"Dog name: {dog.name}")
print(f"Dog speaks: {dog.speak()}")
print(f"Dog describes: {dog.describe()}")
print()
# Create Cat - also inherits everything
cat = Cat("Whiskers")
print(f"Cat name: {cat.name}")
print(f"Cat speaks: {cat.speak()}")
print(f"Cat describes: {cat.describe()}")
print()
# Create Bird - has additional attribute
bird =
print(f"Bird name: {bird.name}")
print(f"Bird can fly: {bird.can_fly}")
print(f"Bird speaks: {bird.speak()}")
print()
# Bird that can't fly
penguin = Bird("Pingu", can_fly=False)
print(f"Penguin name: {penguin.name}")
print(f"Penguin can fly: {penguin.can_fly}")
print("\n=== Inheritance Rules ===")
print("""
1. Child inherits all attributes and methods
2. Syntax: class Child(Parent):
3. super().__init__() calls parent's __init__
4. Child can add new attributes/methods
5. 'pass' means no additions (pure inheritance)
""")
# Basic Inheritance
class Animal:
"""Base class for all animals."""
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
def describe(self):
return f"I am {self.name}"
# Dog inherits from Animal
class Dog(Animal):
"""Dog class inherits from Animal."""
pass # No additional code - inherits everything
# Cat inherits from Animal
class Cat(Animal):
"""Cat class inherits from Animal."""
pass
# Bird with additional attribute
class Bird(Animal):
def __init__(self, name, can_fly=True):
# Call parent's __init__
super().__init__(name)
self.can_fly = can_fly
# Demonstrate basic inheritance
print("=== Basic Inheritance ===\n")
# Create Dog - uses inherited __init__
dog =
print(f"Dog name: {dog.name}")
print(f"Dog speaks: {dog.speak()}")
print(f"Dog describes: {dog.describe()}")
print()
# Create Cat - also inherits everything
cat = Cat("Whiskers")
print(f"Cat name: {cat.name}")
print(f"Cat speaks: {cat.speak()}")
print(f"Cat describes: {cat.describe()}")
print()
# Create Bird - has additional attribute
bird =
print(f"Bird name: {bird.name}")
print(f"Bird can fly: {bird.can_fly}")
print(f"Bird speaks: {bird.speak()}")
print()
# Bird that can't fly
penguin = Bird("Pingu", can_fly=False)
print(f"Penguin name: {penguin.name}")
print(f"Penguin can fly: {penguin.can_fly}")
print("\n=== Inheritance Rules ===")
print("""
1. Child inherits all attributes and methods
2. Syntax: class Child(Parent):
3. super().__init__() calls parent's __init__
4. Child can add new attributes/methods
5. 'pass' means no additions (pure inheritance)
""")
# Basic Inheritance
class Animal:
"""Base class for all animals."""
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
def describe(self):
return f"I am {self.name}"
# Dog inherits from Animal
class Dog(Animal):
"""Dog class inherits from Animal."""
pass # No additional code - inherits everything
# Cat inherits from Animal
class Cat(Animal):
"""Cat class inherits from Animal."""
pass
# Bird with additional attribute
class Bird(Animal):
def __init__(self, name, can_fly=True):
# Call parent's __init__
super().__init__(name)
self.can_fly = can_fly
# Demonstrate basic inheritance
print("=== Basic Inheritance ===\n")
# Create Dog - uses inherited __init__
dog =
print(f"Dog name: {dog.name}")
print(f"Dog speaks: {dog.speak()}")
print(f"Dog describes: {dog.describe()}")
print()
# Create Cat - also inherits everything
cat = Cat("Whiskers")
print(f"Cat name: {cat.name}")
print(f"Cat speaks: {cat.speak()}")
print(f"Cat describes: {cat.describe()}")
print()
# Create Bird - has additional attribute
bird =
print(f"Bird name: {bird.name}")
print(f"Bird can fly: {bird.can_fly}")
print(f"Bird speaks: {bird.speak()}")
print()
# Bird that can't fly
penguin = Bird("Pingu", can_fly=False)
print(f"Penguin name: {penguin.name}")
print(f"Penguin can fly: {penguin.can_fly}")
print("\n=== Inheritance Rules ===")
print("""
1. Child inherits all attributes and methods
2. Syntax: class Child(Parent):
3. super().__init__() calls parent's __init__
4. Child can add new attributes/methods
5. 'pass' means no additions (pure inheritance)
""")
class Child(Parent): inherits all of Parent's methods and attributes.
Call parent constructor
Initialize parent attributes with super().
# Using super() to Call Parent's __init__
class Person:
"""Base class for people."""
def __init__(self, name, age):
self.name = name
self.age = age
print(f" Person.__init__ called: {name}, {age}")
def introduce(self):
return f"I'm {self.name}, {self.age} years old"
class Student(Person):
"""Student inherits from Person."""
def __init__(self, name, age, student_id):
print(f" Student.__init__ starting...")
super().__init__(name, age)
self.student_id = student_id
print(f" Student.__init__ completed: id={student_id}")
def introduce(self):
return f"I'm {self.name}, student #{self.student_id}"
class Employee(Person):
"""Employee inherits from Person."""
def __init__(self, name, age, department, salary):
super().__init__(name, age)
self.department = department
self.salary = salary
def introduce(self):
return f"I'm {self.name} from {self.department}"
class Manager(Employee):
"""Manager inherits from Employee (which inherits from Person)."""
def __init__(self, name, age, department, salary, team_size):
# super() calls Employee.__init__
super().__init__(name, age, department, salary)
self.team_size = team_size
def introduce(self):
return f"I'm {self.name}, managing {self.team_size} people in {self.department}"
print("=== Understanding super().__init__() ===\n")
# Simple case: Student
print("Creating Student:")
student = Student("Alice", 20, "S12345")
print(f"Result: {student.introduce()}")
print(f"Has name: {student.name}")
print(f"Has age: {student.age}")
print(f"Has student_id: {student.student_id}")
print("\n" + "="*40 + "\n")
# Employee case
print("Creating Employee:")
emp = Employee("Bob", 35, "Engineering", 75000)
print(f"Result: {emp.introduce()}")
print(f"Has name: {emp.name}")
print(f"Has age: {emp.age}")
print(f"Has department: {emp.department}")
print(f"Has salary: {emp.salary}")
print("\n" + "="*40 + "\n")
# Inheritance chain: Manager -> Employee -> Person
print("Creating Manager (three-level inheritance):")
manager = Manager("Carol", 45, "Engineering", 120000, 8)
print(f"Result: {manager.introduce()}")
print(f"Has name: {manager.name} (from Person)")
print(f"Has age: {manager.age} (from Person)")
print(f"Has department: {manager.department} (from Employee)")
print(f"Has salary: {manager.salary} (from Employee)")
print(f"Has team_size: {manager.team_size} (from Manager)")
print("\n=== super() Key Points ===")
print("""
1. super() returns a proxy to the parent class
2. super().__init__() calls parent's constructor
3. Must pass required arguments to parent
4. Each level in chain calls its parent
5. Attributes accumulate through the chain:
Manager has: name, age (Person)
+ department, salary (Employee)
+ team_size (Manager)
""")
super().__init__(args) calls parent's __init__. Always call it first.
Override methods
Replace parent behavior with child-specific implementation.
# Method Overriding
class Shape:
"""Base class for geometric shapes."""
def __init__(self, name):
self.name = name
def area(self):
return 0 # Default: no area
def perimeter(self):
return 0 # Default: no perimeter
def describe(self):
return f"I am a {self.name}"
class Rectangle(Shape):
"""Rectangle overrides area and perimeter."""
def __init__(self, width, height):
super().__init__("Rectangle")
self.width = width
self.height = height
# Override area method
def area(self):
return self.width * self.height
# Override perimeter method
def perimeter(self):
return 2 * (self.width + self.height)
# Override describe
def describe(self):
return f"Rectangle: {self.width} x {self.height}"
class Circle(Shape):
"""Circle overrides area and perimeter."""
PI = 3.14159
def __init__(self, radius):
super().__init__("Circle")
self.radius = radius
def area(self):
return Circle.PI * self.radius ** 2
def perimeter(self):
return 2 * Circle.PI * self.radius
def describe(self):
return f"Circle: radius = {self.radius}"
class Triangle(Shape):
"""Triangle overrides methods."""
def __init__(self, a, b, c):
super().__init__("Triangle")
self.a = a # Side lengths
self.b = b
self.c = c
def area(self):
# 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):
return self.a + self.b + self.c
def describe(self):
return f"Triangle: sides {self.a}, {self.b}, {self.c}"
# Demonstration
print("=== Method Overriding ===\n")
# Create shapes
shapes = [
Rectangle(5, 3),
Circle(4),
Triangle(3, 4, 5),
]
# Polymorphic behavior - same method, different results
for shape in shapes:
print(shape.describe())
print(f" Area: {shape.area():.2f}")
print(f" Perimeter: {shape.perimeter():.2f}")
print()
# The base Shape class
print("Base Shape class (no override):")
generic = Shape("Unknown")
print(f" {generic.describe()}")
print(f" Area: {generic.area()}")
print(f" Perimeter: {generic.perimeter()}")
print("\n=== Override Rules ===")
print("""
1. Same method name as parent
2. Same parameters (usually)
3. Different implementation
4. Child's version is called on child objects
5. Parent's version still exists in parent objects
6. No special keyword needed (unlike Java's @Override)
""")
Same method name in child replaces parent's version.
Extend parent methods
Add to parent behavior instead of replacing it.
# Extending Parent Methods with super()
class Logger:
"""Base logger class."""
def __init__(self):
self.logs = []
def log(self, message):
"""Basic logging - just store message."""
entry = f"LOG: {message}"
self.logs.append(entry)
return entry
class TimestampLogger(Logger):
"""Logger that adds timestamps."""
def log(self, message):
# Add timestamp, then call parent
from datetime import datetime
timestamp = datetime.now().strftime("%H:%M:%S")
timestamped_message = f"[{timestamp}] {message}"
# Call parent's log with enhanced message
return super().log(timestamped_message)
class LevelLogger(Logger):
"""Logger with severity levels."""
def log(self, message, level="INFO"):
# Add level prefix, then call parent
leveled_message = f"{level}: {message}"
return super().log(leveled_message)
# Convenience methods
def info(self, message):
return self.log(message, "INFO")
def warning(self, message):
return self.log(message, "WARNING")
def error(self, message):
return self.log(message, "ERROR")
class CountingLogger(Logger):
"""Logger that counts messages."""
def __init__(self):
super().__init__()
self.count = 0
def log(self, message):
self.count += 1
# Extend message with count
numbered_message = f"#{self.count} {message}"
return super().log(numbered_message)
class FullFeaturedLogger(Logger):
"""Logger combining multiple features."""
def __init__(self, name):
super().__init__()
self.name = name
self.count = 0
def log(self, message, level="INFO"):
from datetime import datetime
self.count += 1
# Build full formatted message
timestamp = datetime.now().strftime("%H:%M:%S")
formatted = f"[{timestamp}] [{self.name}] #{self.count} {level}: {message}"
# Still use parent's storage mechanism
return super().log(formatted)
# Demonstration
print("=== Extending Parent Methods ===\n")
# Basic logger
print("--- Basic Logger ---")
basic = Logger()
print(basic.log("Hello"))
print(basic.log("World"))
print(f"Total logs: {len(basic.logs)}")
print("\n--- Timestamp Logger ---")
ts_logger = TimestampLogger()
print(ts_logger.log("Application started"))
print(ts_logger.log("Processing data"))
print(f"Logs stored: {ts_logger.logs}")
print("\n--- Level Logger ---")
level_logger = LevelLogger()
print(level_logger.info("Normal operation"))
print(level_logger.warning("Low disk space"))
print(level_logger.error("Connection failed"))
print("\n--- Counting Logger ---")
count_logger = CountingLogger()
print(count_logger.log("First message"))
print(count_logger.log("Second message"))
print(count_logger.log("Third message"))
print(f"Total count: {count_logger.count}")
print("\n--- Full Featured Logger ---")
app_logger = FullFeaturedLogger("APP")
print(app_logger.log("Started", "INFO"))
print(app_logger.log("Processing", "DEBUG"))
print(app_logger.log("Something wrong", "ERROR"))
print("\n=== Extend vs Override ===")
print("""
OVERRIDE: Replace parent's behavior completely
def method(self):
# all new code
EXTEND: Add to parent's behavior
def method(self):
# do something extra
super().method() # then call parent
# optionally do more
Benefits of extending:
1. Reuse parent's logic
2. Add functionality without duplicating code
3. Parent changes automatically apply
4. Can pre-process OR post-process
""")
Call super().method() then add more logic. Best of both worlds.
Type checking with isinstance
Check inheritance relationships at runtime.
# isinstance() and issubclass()
class Vehicle:
"""Base class for all vehicles."""
def __init__(self, brand):
self.brand = brand
def start(self):
return f"{self.brand} starting..."
class Car(Vehicle):
"""Car extends Vehicle."""
def __init__(self, brand, num_doors):
super().__init__(brand)
self.num_doors = num_doors
def drive(self):
return f"{self.brand} car driving"
class ElectricCar(Car):
"""ElectricCar extends Car."""
def __init__(self, brand, num_doors, battery_capacity):
super().__init__(brand, num_doors)
self.battery_capacity = battery_capacity
def charge(self):
return f"Charging {self.brand}"
class Motorcycle(Vehicle):
"""Motorcycle extends Vehicle."""
def __init__(self, brand, cc):
super().__init__(brand)
self.cc = cc
def wheelie(self):
return f"{self.brand} doing a wheelie!"
print("=== isinstance() and issubclass() ===\n")
# Create instances
my_car = Car("Toyota", 4)
my_tesla = ElectricCar("Tesla", 4, 100)
my_bike = Motorcycle("Harley", 1200)
print("--- isinstance() checks ---")
# isinstance(object, class)
print(f"my_car is Car: {isinstance(my_car, Car)}")
print(f"my_car is Vehicle: {isinstance(my_car, Vehicle)}")
print(f"my_car is ElectricCar: {isinstance(my_car, ElectricCar)}")
print()
# Tesla checks
print(f"my_tesla is ElectricCar: {isinstance(my_tesla, ElectricCar)}")
print(f"my_tesla is Car: {isinstance(my_tesla, Car)}")
print(f"my_tesla is Vehicle: {isinstance(my_tesla, Vehicle)}")
print(f"my_tesla is Motorcycle: {isinstance(my_tesla, Motorcycle)}")
print()
# Check multiple types
print(f"my_car is (Car, Motorcycle): {isinstance(my_car, (Car, Motorcycle))}")
print(f"my_bike is (Car, Motorcycle): {isinstance(my_bike, (Car, Motorcycle))}")
print("\n--- issubclass() checks ---")
# issubclass(class, class)
print(f"Car is subclass of Vehicle: {issubclass(Car, Vehicle)}")
print(f"ElectricCar is subclass of Car: {issubclass(ElectricCar, Car)}")
print(f"ElectricCar is subclass of Vehicle: {issubclass(ElectricCar, Vehicle)}")
print(f"Car is subclass of Car: {issubclass(Car, Car)}")
print(f"Car is subclass of Motorcycle: {issubclass(Car, Motorcycle)}")
print("\n--- Practical use: Processing different types ---")
# List of mixed vehicles
vehicles = [my_car, my_tesla, my_bike]
for v in vehicles:
print(f"\n{v.brand}:")
print(f" {v.start()}")
# Type-specific behavior
if isinstance(v, ElectricCar):
print(f" {v.charge()}")
if isinstance(v, Car):
print(f" {v.drive()}")
if isinstance(v, Motorcycle):
print(f" {v.wheelie()}")
print("\n--- Type hierarchy ---")
print(f"ElectricCar MRO: {ElectricCar.__mro__}")
print("\n=== isinstance vs type() ===")
print("""
isinstance(obj, cls):
- Returns True if obj is instance of cls OR subclass
- Preferred for inheritance hierarchies
- Can check multiple types with tuple
type(obj) == cls:
- Returns True only for exact type match
- Does NOT consider inheritance
- Use when exact type matters
Example:
isinstance(my_tesla, Car) # True (Tesla IS-A Car)
type(my_tesla) == Car # False (exact type is ElectricCar)
""")
# Demonstrate difference
print("--- isinstance vs type ===")
print(f"isinstance(my_tesla, Car): {isinstance(my_tesla, Car)}")
print(f"type(my_tesla) == Car: {type(my_tesla) == Car}")
print(f"type(my_tesla) == ElectricCar: {type(my_tesla) == ElectricCar}")
isinstance(obj, Class) checks if obj is instance of Class or subclass.
Exercise: practical.py
Build an employee hierarchy with inheritance