Chapter 22 Advanced 60 Questions

Practice Questions — Inheritance and Polymorphism

← Back to Notes
7 Easy
10 Medium
10 Hard

Topic-Specific Questions

Question 1
Easy
What is the output of the following code?
class Animal:
    def speak(self):
        return "..."

class Dog(Animal):
    def speak(self):
        return "Woof!"

d = Dog()
print(d.speak())
Dog overrides the speak method from Animal.
Woof!
Question 2
Easy
What is the output?
class Parent:
    def __init__(self, name):
        self.name = name

class Child(Parent):
    pass

c = Child("Aarav")
print(c.name)
Child inherits __init__ from Parent because it does not define its own.
Aarav
Question 3
Easy
What is the output?
class A:
    x = 10

class B(A):
    pass

class C(A):
    x = 20

print(B.x)
print(C.x)
B inherits x from A. C overrides x with its own value.
10
20
Question 4
Easy
What is the output?
class Vehicle:
    def type(self):
        return "Vehicle"

class Car(Vehicle):
    def type(self):
        return "Car"

c = Car()
print(isinstance(c, Car))
print(isinstance(c, Vehicle))
isinstance checks if the object is an instance of the class or its parents.
True
True
Question 5
Easy
What is the output?
class Base:
    def greet(self):
        return "Hello from Base"

class Derived(Base):
    def greet(self):
        return "Hello from Derived"

b = Base()
d = Derived()
print(b.greet())
print(d.greet())
Each object calls its own class's version of greet().
Hello from Base
Hello from Derived
Question 6
Medium
What is the output?
class Person:
    def __init__(self, name):
        self.name = name
    
    def info(self):
        return self.name

class Student(Person):
    def __init__(self, name, grade):
        super().__init__(name)
        self.grade = grade
    
    def info(self):
        return f"{super().info()} (Grade: {self.grade})"

s = Student("Priya", "A+")
print(s.info())
super().info() calls Person's info method.
Priya (Grade: A+)
Question 7
Medium
What is the output?
class A:
    def method(self):
        return "A"

class B(A):
    def method(self):
        return "B"

class C(A):
    def method(self):
        return "C"

class D(B, C):
    pass

print(D().method())
print([cls.__name__ for cls in D.__mro__])
D inherits from B first, then C. The MRO checks B before C.
B
['D', 'B', 'C', 'A', 'object']
Question 8
Medium
What is the output?
class X:
    def show(self):
        return "X"

class Y(X):
    pass

class Z(X):
    def show(self):
        return "Z"

class W(Y, Z):
    pass

print(W().show())
Y does not define show(). Check the MRO to see where Python looks next.
Z
Question 9
Medium
What is the output?
class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return "..."

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

animals = [Dog("Rex"), Cat("Luna"), Dog("Buddy")]
for a in animals:
    print(f"{a.name}: {a.speak()}")
Each object calls its own class's speak() method.
Rex: Woof!
Luna: Meow!
Buddy: Woof!
Question 10
Medium
What is the output?
class Base:
    def __init__(self):
        print("Base.__init__")

class Middle(Base):
    def __init__(self):
        print("Middle.__init__")
        super().__init__()

class Child(Middle):
    def __init__(self):
        print("Child.__init__")
        super().__init__()

c = Child()
super() calls the next class in the MRO.
Child.__init__
Middle.__init__
Base.__init__
Question 11
Hard
What is the output?
class A:
    def __init__(self):
        print("A", end=" ")

class B(A):
    def __init__(self):
        print("B", end=" ")
        super().__init__()

class C(A):
    def __init__(self):
        print("C", end=" ")
        super().__init__()

class D(B, C):
    def __init__(self):
        print("D", end=" ")
        super().__init__()

d = D()
print()
MRO: D -> B -> C -> A. super() follows the MRO, not the direct parent.
D B C A
Question 12
Hard
What is the output?
class Animal:
    def __init__(self, name):
        self.name = name

class Pet:
    def __init__(self, owner):
        self.owner = owner

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed

d = Dog("Rex", "Labrador")
print(d.name)
print(d.breed)
print(issubclass(Dog, Animal))
print(issubclass(Dog, Pet))
Dog inherits from Animal only, not from Pet.
Rex
Labrador
True
False
Question 13
Hard
What is the output?
class Validator:
    def validate(self, value):
        return True

class RangeValidator(Validator):
    def __init__(self, low, high):
        self.low = low
        self.high = high
    
    def validate(self, value):
        return self.low <= value <= self.high

class TypeValidator(Validator):
    def __init__(self, expected_type):
        self.expected_type = expected_type
    
    def validate(self, value):
        return isinstance(value, self.expected_type)

validators = [RangeValidator(1, 100), TypeValidator(int)]
test_values = [50, 200, "hello"]

for val in test_values:
    results = [v.validate(val) for v in validators]
    print(f"{val!r}: {results}")
Each validator checks independently. Follow the logic for each value.
50: [True, True]
200: [False, True]
'hello': [False, False]
Question 14
Hard
What is the output?
class Base:
    def method(self):
        return self.value()
    
    def value(self):
        return 1

class Child(Base):
    def value(self):
        return 2

b = Base()
c = Child()
print(b.method())
print(c.method())
self.value() calls the version of value() based on the actual type of self.
1
2
Question 15
Hard
What is the output?
class Counter:
    def __init__(self, start=0):
        self.count = start
    
    def __add__(self, other):
        return Counter(self.count + other.count)
    
    def __str__(self):
        return f"Counter({self.count})"

a = Counter(10)
b = Counter(20)
c = a + b
print(c)
print(type(c).__name__)
__add__ creates a new Counter with the sum of counts.
Counter(30)
Counter
Question 16
Medium
What is the Method Resolution Order (MRO) and why does it matter?
Think about multiple inheritance and which method Python calls when there are multiple options.
MRO is the order in which Python searches classes for a method when using inheritance. It is computed using the C3 linearization algorithm. MRO matters because in multiple inheritance, a method might exist in several parent classes, and the MRO determines which one is called. You can inspect it with ClassName.__mro__ or ClassName.mro().
Question 17
Hard
In the diamond problem, why does super().__init__() in B's __init__ call C's __init__ instead of A's?
super() follows the MRO, not the direct parent.
In a diamond inheritance (D -> B, C -> A), the MRO is D -> B -> C -> A. When B's __init__ calls super().__init__(), Python looks at the MRO relative to the current object (which is a D instance). The next class after B in D's MRO is C, not A. So super() in B delegates to C, ensuring each class's __init__ is called exactly once.

Mixed & Application Questions

Question 1
Easy
What is the output?
class Shape:
    def area(self):
        return 0

class Square(Shape):
    def __init__(self, side):
        self.side = side
    
    def area(self):
        return self.side ** 2

s = Square(5)
print(s.area())
print(isinstance(s, Shape))
Square overrides area() and inherits from Shape.
25
True
Question 2
Easy
What is the output?
class Flyable:
    def fly(self):
        return f"{self.name} can fly"

class Swimmable:
    def swim(self):
        return f"{self.name} can swim"

class Duck(Flyable, Swimmable):
    def __init__(self, name):
        self.name = name

d = Duck("Donald")
print(d.fly())
print(d.swim())
Duck inherits methods from both Flyable and Swimmable.
Donald can fly
Donald can swim
Question 3
Medium
What is the output?
class Logger:
    def log(self, message):
        print(f"[LOG] {message}")

class Database(Logger):
    def save(self, data):
        self.log(f"Saving: {data}")
        return True

class Cache(Logger):
    def store(self, key, value):
        self.log(f"Caching: {key}={value}")

db = Database()
cache = Cache()
db.save("user_data")
cache.store("name", "Aarav")
Both Database and Cache inherit log() from Logger.
[LOG] Saving: user_data
[LOG] Caching: name=Aarav
Question 4
Medium
What is the output?
class A:
    def __str__(self):
        return "A"

class B(A):
    pass

class C(A):
    def __str__(self):
        return "C"

class D(B, C):
    pass

print(D())
D does not define __str__. Follow the MRO: D -> B -> C -> A.
C
Question 5
Medium
What is the output?
class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary
    
    def pay(self):
        return self.salary

class Manager(Employee):
    def __init__(self, name, salary, bonus):
        super().__init__(name, salary)
        self.bonus = bonus
    
    def pay(self):
        return super().pay() + self.bonus

m = Manager("Aarav", 50000, 10000)
print(m.pay())
print(m.name)
Manager.pay() calls super().pay() which returns the base salary.
60000
Aarav
Question 6
Hard
What is the output?
class Iterable:
    def __init__(self, data):
        self.data = data
    
    def __len__(self):
        return len(self.data)
    
    def __getitem__(self, index):
        return self.data[index]

class NameList(Iterable):
    def __contains__(self, name):
        return name.lower() in [n.lower() for n in self.data]

names = NameList(["Aarav", "Priya", "Rohan"])
print(len(names))
print(names[1])
print("PRIYA" in names)
print("Meera" in names)
__contains__ does case-insensitive checking.
3
Priya
True
False
Question 7
Hard
What is the output?
class Base:
    count = 0
    
    def __init__(self):
        Base.count += 1

class ChildA(Base):
    pass

class ChildB(Base):
    pass

a1 = ChildA()
a2 = ChildA()
b1 = ChildB()

print(Base.count)
print(ChildA.count)
print(ChildB.count)
All __init__ calls increment Base.count. It is a shared class variable.
3
3
3
Question 8
Hard
What is the output?
class JsonMixin:
    def to_dict(self):
        return self.__dict__.copy()

class Person(JsonMixin):
    def __init__(self, name, age):
        self.name = name
        self.age = age

class Student(Person):
    def __init__(self, name, age, grade):
        super().__init__(name, age)
        self.grade = grade

s = Student("Aarav", 16, "A")
print(s.to_dict())
print(isinstance(s, JsonMixin))
Student inherits from Person which inherits from JsonMixin.
{'name': 'Aarav', 'age': 16, 'grade': 'A'}
True
Question 9
Medium
What is duck typing and how does it relate to polymorphism?
Think about what Python checks when you call a method on an object.
Duck typing is Python's approach to type checking: if an object has the right methods, it works, regardless of its actual class. 'If it walks like a duck and quacks like a duck, it is a duck.' This is a form of polymorphism that does not require inheritance. Any object with a speak() method can be used where speak() is called, whether it inherits from a common base or not.
Question 10
Hard
What is the difference between method overriding and method overloading? Does Python support both?
Overriding: same method name in parent and child. Overloading: same name with different parameter lists.
Method overriding is when a child class defines a method with the same name as the parent's, replacing its behavior. Python fully supports this. Method overloading is when multiple methods have the same name but different parameter lists (common in Java). Python does NOT support traditional overloading -- defining a method twice replaces the first definition. Python achieves similar flexibility through default parameters, *args, and **kwargs.

Multiple Choice Questions

MCQ 1
What does class Dog(Animal) mean?
  • A. Dog is a function that returns an Animal
  • B. Dog inherits from Animal
  • C. Dog is the same as Animal
  • D. Dog contains an Animal object
Answer: B
B is correct. The parentheses indicate inheritance: Dog is a subclass of Animal. It inherits all attributes and methods from Animal.
MCQ 2
What does super() do?
  • A. Creates a new superclass
  • B. Returns a proxy that delegates method calls to the parent class
  • C. Deletes the current class
  • D. Makes the current class abstract
Answer: B
B is correct. super() returns a proxy object that delegates method calls to the parent (or next class in the MRO). Most commonly used as super().__init__().
MCQ 3
What is method overriding?
  • A. Defining a method with the same name in a child class that replaces the parent's version
  • B. Defining multiple methods with the same name but different parameters
  • C. Calling a parent method from a child
  • D. Deleting a method from the parent class
Answer: A
A is correct. Method overriding occurs when a child class defines a method with the same name as the parent's. The child's version is used for objects of the child class.
MCQ 4
What is polymorphism?
  • A. A class that cannot be instantiated
  • B. The ability to use the same method name with different types, each producing different behavior
  • C. A method that takes multiple parameters
  • D. A class with multiple constructors
Answer: B
B is correct. Polymorphism means 'many forms.' The same method name (e.g., area()) works differently for different object types (Circle, Rectangle, Triangle).
MCQ 5
In Python, all classes implicitly inherit from which class?
  • A. Base
  • B. Object
  • C. object
  • D. None
Answer: C
C is correct. In Python 3, all classes implicitly inherit from object (lowercase). class MyClass: is equivalent to class MyClass(object):.
MCQ 6
What is the MRO for class D(B, C) where B and C both inherit from A?
  • A. D -> A -> B -> C
  • B. D -> B -> A -> C
  • C. D -> B -> C -> A -> object
  • D. D -> C -> B -> A -> object
Answer: C
C is correct. C3 linearization gives D -> B -> C -> A -> object. B is listed before C in the class definition, so it comes first. A comes after both B and C because it is their common parent.
MCQ 7
What does isinstance(obj, cls) check?
  • A. If obj is exactly of type cls
  • B. If obj is an instance of cls or any of its subclasses
  • C. If cls is a subclass of obj's class
  • D. If obj and cls are the same object
Answer: B
B is correct. isinstance(obj, cls) returns True if obj is an instance of cls or any class that inherits from cls. It checks the full inheritance chain.
MCQ 8
What is duck typing?
  • A. A naming convention for variables
  • B. A type of inheritance
  • C. If an object has the required methods, it can be used regardless of its class
  • D. A way to convert types automatically
Answer: C
C is correct. Duck typing means Python does not check an object's class to determine if it has the right interface. If the required methods exist, Python uses them. 'If it walks like a duck and quacks like a duck, it is a duck.'
MCQ 9
What happens if you try to instantiate a class with an unimplemented @abstractmethod?
  • A. The abstract methods return None
  • B. TypeError is raised
  • C. The methods are automatically created
  • D. Nothing, it works normally
Answer: B
B is correct. You cannot instantiate a class that has unimplemented abstract methods. Python raises TypeError: Can't instantiate abstract class X with abstract method Y.
MCQ 10
What is a mixin?
  • A. A class that mixes two variables together
  • B. A class designed to be inherited alongside other classes, providing specific functionality
  • C. A function that combines two classes
  • D. A type of abstract class
Answer: B
B is correct. A mixin is a small class that provides specific functionality (like serialization, logging) and is designed to be inherited alongside a main class. Mixins typically do not have __init__ and are not meant to be used alone.
MCQ 11
What does super().__init__() call in a diamond inheritance D(B, C) when called from B?
  • A. A's __init__ (B's direct parent)
  • B. C's __init__ (next in MRO after B)
  • C. D's __init__ (the original caller)
  • D. object's __init__
Answer: B
B is correct. super() follows the MRO, not the direct parent. For a D instance, the MRO is D->B->C->A->object. When B calls super().__init__(), the next class after B in the MRO is C, so C's __init__ is called.
MCQ 12
What is the purpose of returning NotImplemented from __add__?
  • A. To raise a NotImplementedError
  • B. To signal that this operand cannot handle the operation, letting Python try the other operand's __radd__
  • C. To return None
  • D. To prevent addition entirely
Answer: B
B is correct. Returning NotImplemented tells Python that this operand does not support the operation. Python then tries the right operand's __radd__ method. This is different from raising NotImplementedError.
MCQ 13
What is the difference between issubclass() and isinstance()?
  • A. They do the same thing
  • B. issubclass checks class relationships; isinstance checks object-class relationships
  • C. isinstance checks class relationships; issubclass checks object-class relationships
  • D. issubclass only works with abstract classes
Answer: B
B is correct. issubclass(Dog, Animal) checks if the Dog class inherits from Animal. isinstance(d, Animal) checks if the object d is an instance of Animal or its subclasses.
MCQ 14
What is the __add__ method used for?
  • A. Adding attributes to a class
  • B. Defining the behavior of the + operator for objects of the class
  • C. Adding methods to a class at runtime
  • D. Concatenating strings
Answer: B
B is correct. __add__(self, other) defines what happens when you use the + operator with objects of the class: a + b calls a.__add__(b).
MCQ 15
If a child class does not define __init__, what happens when you create an instance?
  • A. TypeError is raised
  • B. The parent's __init__ is called
  • C. The object is created with no attributes
  • D. Python creates a default __init__
Answer: B
B is correct. If the child does not define __init__, it inherits the parent's __init__. The parent's initialization code runs as if you were creating a parent instance, setting all the parent's attributes.
MCQ 16
What does the abc module provide?
  • A. Alphabet-related string operations
  • B. Abstract Base Classes for defining interfaces
  • C. Automatic backup and cleanup utilities
  • D. Advanced boolean comparison tools
Answer: B
B is correct. The abc module provides ABC class and @abstractmethod decorator for creating Abstract Base Classes. ABCs define interfaces that subclasses must implement.
MCQ 17
Can a class inherit from multiple abstract base classes?
  • A. No, only one ABC is allowed
  • B. Yes, but it must implement all abstract methods from all parents
  • C. Yes, but abstract methods are optional from the second parent
  • D. No, abstract classes cannot be used in multiple inheritance
Answer: B
B is correct. A class can inherit from multiple ABCs. It must implement every @abstractmethod from every abstract parent. If any are missing, the class itself becomes abstract and cannot be instantiated.
MCQ 18
What special method enables the len() function on custom objects?
  • A. __size__
  • B. __length__
  • C. __len__
  • D. __count__
Answer: C
C is correct. Defining __len__(self) allows len(obj) to work on custom objects. It should return a non-negative integer.

Coding Challenges

Challenge 1: Shape Hierarchy

Easy
Create a Shape base class with an area() method returning 0. Create Circle(radius), Rectangle(width, height), and Triangle(base, height) subclasses, each overriding area(). Write a function total_area(shapes) that returns the sum of all areas.
Sample Input
(No input required)
Sample Output
Circle: 78.54 Rectangle: 24 Triangle: 12.0 Total: 114.54
Use inheritance and method overriding. The function should work with any list of Shape objects.
class Shape:
    def area(self):
        return 0
    
    def __str__(self):
        return f"{type(self).__name__}: {self.area()}"

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    def area(self):
        return round(3.14159 * self.radius ** 2, 2)

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    def area(self):
        return self.width * self.height

class Triangle(Shape):
    def __init__(self, base, height):
        self.base = base
        self.height = height
    def area(self):
        return 0.5 * self.base * self.height

def total_area(shapes):
    return sum(s.area() for s in shapes)

shapes = [Circle(5), Rectangle(4, 6), Triangle(6, 4)]
for s in shapes:
    print(s)
print(f"Total: {total_area(shapes)}")

Challenge 2: Employee Pay System

Easy
Create an Employee base class with name and base_salary. Create Manager (with bonus), Developer (with overtime_hours at 500/hr), and Intern (gets 50% of base). Each overrides calculate_pay(). Print payroll for a team.
Sample Input
(No input required)
Sample Output
Aarav (Manager): Rs.60000 Priya (Developer): Rs.45000 Rohan (Intern): Rs.10000 Total payroll: Rs.115000
Use super().__init__() in all subclasses.
class Employee:
    def __init__(self, name, base_salary):
        self.name = name
        self.base_salary = base_salary
    
    def calculate_pay(self):
        return self.base_salary
    
    def role(self):
        return type(self).__name__

class Manager(Employee):
    def __init__(self, name, base_salary, bonus):
        super().__init__(name, base_salary)
        self.bonus = bonus
    
    def calculate_pay(self):
        return self.base_salary + self.bonus

class Developer(Employee):
    def __init__(self, name, base_salary, overtime_hours):
        super().__init__(name, base_salary)
        self.overtime_hours = overtime_hours
    
    def calculate_pay(self):
        return self.base_salary + self.overtime_hours * 500

class Intern(Employee):
    def calculate_pay(self):
        return int(self.base_salary * 0.5)

team = [Manager("Aarav", 50000, 10000), Developer("Priya", 40000, 10), Intern("Rohan", 20000)]
total = 0
for emp in team:
    pay = emp.calculate_pay()
    total += pay
    print(f"{emp.name} ({emp.role()}): Rs.{pay}")
print(f"Total payroll: Rs.{total}")

Challenge 3: Animal Sound Polymorphism

Easy
Create an Animal base class and at least 4 animal subclasses (Dog, Cat, Cow, Duck), each with a sound() method. Write a function animal_chorus(animals) that calls sound() on each. Demonstrate duck typing by adding a Robot class (not inheriting from Animal) that also has sound().
Sample Input
(No input required)
Sample Output
Rex the Dog says: Woof! Luna the Cat says: Meow! Gauri the Cow says: Moo! Daisy the Duck says: Quack! R2D2 the Robot says: Beep!
Demonstrate polymorphism and duck typing.
class Animal:
    def __init__(self, name):
        self.name = name
    def sound(self):
        return "..."
    def __str__(self):
        return f"{self.name} the {type(self).__name__} says: {self.sound()}"

class Dog(Animal):
    def sound(self): return "Woof!"

class Cat(Animal):
    def sound(self): return "Meow!"

class Cow(Animal):
    def sound(self): return "Moo!"

class Duck(Animal):
    def sound(self): return "Quack!"

class Robot:  # Not an Animal!
    def __init__(self, name):
        self.name = name
    def sound(self):
        return "Beep!"
    def __str__(self):
        return f"{self.name} the Robot says: {self.sound()}"

def animal_chorus(things):
    for thing in things:
        print(thing)

animal_chorus([Dog("Rex"), Cat("Luna"), Cow("Gauri"), Duck("Daisy"), Robot("R2D2")])

Challenge 4: Sortable Collection with Operator Overloading

Medium
Create a Student class with name and gpa. Implement __lt__ (for sorting), __eq__, __str__, and __repr__. Create a list of students and sort them by GPA (descending). Also implement __add__ that merges two student lists.
Sample Input
(No input required)
Sample Output
Sorted by GPA (desc): 1. Priya (GPA: 9.8) 2. Aarav (GPA: 9.2) 3. Meera (GPA: 8.5) 4. Rohan (GPA: 7.9)
Use __lt__ for comparisons. sorted() should work with Student objects.
class Student:
    def __init__(self, name, gpa):
        self.name = name
        self.gpa = gpa
    
    def __lt__(self, other):
        return self.gpa < other.gpa
    
    def __eq__(self, other):
        return isinstance(other, Student) and self.gpa == other.gpa
    
    def __str__(self):
        return f"{self.name} (GPA: {self.gpa})"
    
    def __repr__(self):
        return f"Student('{self.name}', {self.gpa})"

students = [
    Student("Aarav", 9.2),
    Student("Priya", 9.8),
    Student("Rohan", 7.9),
    Student("Meera", 8.5),
]

sorted_students = sorted(students, reverse=True)
print("Sorted by GPA (desc):")
for i, s in enumerate(sorted_students, 1):
    print(f"{i}. {s}")

Challenge 5: Plugin System with Abstract Base Class

Medium
Create an abstract Plugin class with abstract methods: name(), version(), and execute(data). Create three concrete plugins: UpperCasePlugin, ReversePlugin, and CountPlugin. Write a PluginManager that registers plugins and runs all of them on given data.
Sample Input
(No input required)
Sample Output
Running UpperCase v1.0: HELLO WORLD Running Reverse v1.0: dlrow olleh Running Counter v1.0: 11 characters
Use ABC and @abstractmethod. PluginManager should work with any Plugin subclass.
from abc import ABC, abstractmethod

class Plugin(ABC):
    @abstractmethod
    def name(self): pass
    
    @abstractmethod
    def version(self): pass
    
    @abstractmethod
    def execute(self, data): pass

class UpperCasePlugin(Plugin):
    def name(self): return "UpperCase"
    def version(self): return "1.0"
    def execute(self, data): return data.upper()

class ReversePlugin(Plugin):
    def name(self): return "Reverse"
    def version(self): return "1.0"
    def execute(self, data): return data[::-1]

class CountPlugin(Plugin):
    def name(self): return "Counter"
    def version(self): return "1.0"
    def execute(self, data): return f"{len(data)} characters"

class PluginManager:
    def __init__(self):
        self.plugins = []
    
    def register(self, plugin):
        self.plugins.append(plugin)
    
    def run_all(self, data):
        for plugin in self.plugins:
            result = plugin.execute(data)
            print(f"Running {plugin.name()} v{plugin.version()}: {result}")

pm = PluginManager()
pm.register(UpperCasePlugin())
pm.register(ReversePlugin())
pm.register(CountPlugin())
pm.run_all("hello world")

Challenge 6: MRO Explorer

Medium
Create a diamond inheritance hierarchy: class A at the top, B and C inheriting from A, and D inheriting from both B and C. Each class should have a method who_am_i() that returns its class name plus super().who_am_i(). Print the MRO and the chain of who_am_i() calls.
Sample Input
(No input required)
Sample Output
MRO: D -> B -> C -> A -> object D -> B -> C -> A
Use super() in each class. Handle the base case in A.
class A:
    def who_am_i(self):
        return "A"

class B(A):
    def who_am_i(self):
        return "B -> " + super().who_am_i()

class C(A):
    def who_am_i(self):
        return "C -> " + super().who_am_i()

class D(B, C):
    def who_am_i(self):
        return "D -> " + super().who_am_i()

d = D()
mro_names = [cls.__name__ for cls in D.__mro__]
print(f"MRO: {' -> '.join(mro_names)}")
print(d.who_am_i())

Challenge 7: Mixin-Based Logging System

Hard
Create TimestampMixin (adds timestamp to __str__), SerializableMixin (to_dict method), and ValidatableMixin (abstract validate method). Create a User class using all three mixins. The User should validate that name is non-empty and age is between 0 and 150.
Sample Input
(No input required)
Sample Output
[2026-04-06] User: Aarav, 16 {'name': 'Aarav', 'age': 16} Valid: True Invalid name: Name cannot be empty Invalid age: Age must be between 0 and 150
Each mixin should provide one specific feature. User combines all three.
from abc import ABC, abstractmethod

class TimestampMixin:
    def timestamped_str(self):
        return f"[2026-04-06] {str(self)}"

class SerializableMixin:
    def to_dict(self):
        return {k: v for k, v in self.__dict__.items() if not k.startswith('_')}

class ValidatableMixin(ABC):
    @abstractmethod
    def validate(self):
        pass

class User(TimestampMixin, SerializableMixin, ValidatableMixin):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def validate(self):
        errors = []
        if not self.name or not self.name.strip():
            errors.append("Name cannot be empty")
        if not isinstance(self.age, int) or self.age < 0 or self.age > 150:
            errors.append("Age must be between 0 and 150")
        return (len(errors) == 0, errors)
    
    def __str__(self):
        return f"User: {self.name}, {self.age}"

u1 = User("Aarav", 16)
print(u1.timestamped_str())
print(u1.to_dict())
valid, errors = u1.validate()
print(f"Valid: {valid}")

u2 = User("", 16)
valid, errors = u2.validate()
print(f"Invalid name: {errors[0]}")

u3 = User("Rohan", 200)
valid, errors = u3.validate()
print(f"Invalid age: {errors[0]}")

Challenge 8: Custom Collection with Full Operator Support

Hard
Create a SortedList class that maintains a sorted list of numbers. Implement __len__, __getitem__, __contains__, __add__ (merge two SortedLists), __eq__, __str__, __iter__, and an insert method that maintains sorted order.
Sample Input
(No input required)
Sample Output
SortedList([1, 3, 5, 7, 9]) Length: 5 Index 2: 5 5 in list: True Merged: SortedList([1, 2, 3, 4, 5, 6, 7, 9])
The list must always remain sorted. insert() should place the element in the correct position.
class SortedList:
    def __init__(self, items=None):
        self._data = sorted(items) if items else []
    
    def insert(self, value):
        for i, item in enumerate(self._data):
            if value <= item:
                self._data.insert(i, value)
                return
        self._data.append(value)
    
    def __len__(self):
        return len(self._data)
    
    def __getitem__(self, index):
        return self._data[index]
    
    def __contains__(self, value):
        for item in self._data:
            if item == value:
                return True
            if item > value:
                return False
        return False
    
    def __add__(self, other):
        return SortedList(self._data + other._data)
    
    def __eq__(self, other):
        return isinstance(other, SortedList) and self._data == other._data
    
    def __iter__(self):
        return iter(self._data)
    
    def __str__(self):
        return f"SortedList({self._data})"

s1 = SortedList([5, 1, 9, 3, 7])
print(s1)
print(f"Length: {len(s1)}")
print(f"Index 2: {s1[2]}")
print(f"5 in list: {5 in s1}")

s2 = SortedList([2, 4, 6])
merged = s1 + s2
print(f"Merged: {merged}")

Need to Review the Concepts?

Go back to the detailed notes for this chapter.

Read Chapter Notes

Want to learn Python with a live mentor?

Explore our Python course