Skip to content
Laviyx Learn

Try

Python OOP Practice

Practise classes and objects, constructors, inheritance, polymorphism, encapsulation with properties, and abstract base classes.

Intermediate8 problemsUpdated

Review first: Classes, Objects, Constructors, Inheritance, Polymorphism, Encapsulation, Abstraction

  1. 1.A bank account class

    Intermediate

    Write a BankAccount class with an owner and a balance (default 0). Add deposit(amount) and withdraw(amount) methods. Both must raise a ValueError for amounts that are not positive, and withdraw must also raise one if there is not enough money. Show a deposit, a withdrawal and a failed withdrawal.

    Show hint

    Store values on self in __init__. Raise errors with raise ValueError("message") before changing the balance.

    Show solution
    Python
    class BankAccount:
        def __init__(self, owner, balance=0):
            self.owner = owner
            self.balance = balance
    
        def deposit(self, amount):
            if amount <= 0:
                raise ValueError("Deposit must be positive")
            self.balance += amount
    
        def withdraw(self, amount):
            if amount <= 0:
                raise ValueError("Withdrawal must be positive")
            if amount > self.balance:
                raise ValueError("Insufficient funds")
            self.balance -= amount
    
    
    account = BankAccount("Asha")
    account.deposit(1000)
    account.withdraw(300)
    print(account.owner, account.balance)
    
    try:
        account.withdraw(5000)
    except ValueError as error:
        print("Error:", error)
    print(account.balance)
    Output
    Asha 700
    Error: Insufficient funds
    700
  2. 2.Readable objects

    Intermediate

    Write a Book class with title, author and pages. Add __str__ returning 'Title' by Author and __repr__ returning Book('Title', 'Author', 123). Print a book, then print a list containing it.

    Show hint

    print(obj) uses __str__. Printing a list shows each item's __repr__.

    Show solution
    Python
    class Book:
        def __init__(self, title, author, pages):
            self.title = title
            self.author = author
            self.pages = pages
    
        def __str__(self):
            return f"'{self.title}' by {self.author}"
    
        def __repr__(self):
            return f"Book({self.title!r}, {self.author!r}, {self.pages})"
    
    
    book = Book("Wings of Fire", "A. P. J. Abdul Kalam", 180)
    
    print(book)
    print([book])
    Output
    'Wings of Fire' by A. P. J. Abdul Kalam
    [Book('Wings of Fire', 'A. P. J. Abdul Kalam', 180)]

    Without these methods, Python prints something like <__main__.Book object at 0x...>. A good __repr__ looks like the code that would recreate the object.

  3. 3.Count the objects

    Intermediate

    Write a Student class that keeps a count of how many students have been created, shared by the whole class. Create three students and print the count, plus each student's name.

    Show hint

    A class attribute is defined in the class body and shared. Increase it with Student.count += 1 inside __init__.

    Show solution
    Python
    class Student:
        count = 0
    
        def __init__(self, name):
            self.name = name
            Student.count += 1
    
    
    students = [Student("Asha"), Student("Ravi"), Student("Meera")]
    
    print(Student.count)
    print([s.name for s in students])
    Output
    3
    ['Asha', 'Ravi', 'Meera']

    Writing self.count += 1 instead would create a separate count on each object and leave Student.count at 0.

  4. 4.Inheritance with super()

    Intermediate

    Write an Employee class with name and salary and a describe() method. Then write a Manager subclass that also has a team_size and overrides describe() to add the team size. Reuse the parent's code with super().

    Show hint

    Call super().__init__(name, salary) in the subclass constructor, and super().describe() inside the overriding method.

    Show solution
    Python
    class Employee:
        def __init__(self, name, salary):
            self.name = name
            self.salary = salary
    
        def describe(self):
            return f"{self.name} earns {self.salary}"
    
    
    class Manager(Employee):
        def __init__(self, name, salary, team_size):
            super().__init__(name, salary)
            self.team_size = team_size
    
        def describe(self):
            return super().describe() + f" and manages {self.team_size} people"
    
    
    print(Employee("Ravi", 78000).describe())
    print(Manager("Meera", 95000, 6).describe())
    print(isinstance(Manager("Meera", 95000, 6), Employee))
    Output
    Ravi earns 78000
    Meera earns 95000 and manages 6 people
    True
  5. 5.Polymorphism with shapes

    Intermediate

    Write Circle, Rectangle and Square classes, each with an area() method. Put one of each in a list and print every shape's class name and area (2 decimals), then the total area. Make Square a subclass of Rectangle.

    Show hint

    The loop only calls shape.area(); it does not need to know which class each shape is.

    Show solution
    Python
    import math
    
    
    class Circle:
        def __init__(self, radius):
            self.radius = radius
    
        def area(self):
            return math.pi * self.radius ** 2
    
    
    class Rectangle:
        def __init__(self, width, height):
            self.width = width
            self.height = height
    
        def area(self):
            return self.width * self.height
    
    
    class Square(Rectangle):
        def __init__(self, side):
            super().__init__(side, side)
    
    
    shapes = [Circle(1.5), Rectangle(4, 2.5), Square(3)]
    
    for shape in shapes:
        print(f"{type(shape).__name__}: {shape.area():.2f}")
    
    print(f"Total: {sum(s.area() for s in shapes):.2f}")
    Output
    Circle: 7.07
    Rectangle: 10.00
    Square: 9.00
    Total: 26.07
  6. 6.Protect data with a property

    Intermediate

    Write a Thermostat class whose temperature can be read and set like a normal attribute, but setting a value outside 10–30 raises a ValueError. Set 22, print it, then try to set 45.

    Show hint

    Store the value in self._temperature and expose it with @property and @temperature.setter.

    Show solution
    Python
    class Thermostat:
        def __init__(self, temperature=20):
            self.temperature = temperature  # goes through the setter
    
        @property
        def temperature(self):
            return self._temperature
    
        @temperature.setter
        def temperature(self, value):
            if not 10 <= value <= 30:
                raise ValueError(f"{value} is outside 10-30")
            self._temperature = value
    
    
    t = Thermostat()
    t.temperature = 22
    print(t.temperature)
    
    try:
        t.temperature = 45
    except ValueError as error:
        print("Error:", error)
    print(t.temperature)
    Output
    22
    Error: 45 is outside 10-30
    22

    Assigning self.temperature in __init__ (rather than self._temperature) means even the starting value is validated.

  7. 7.An abstract base class

    Intermediate

    Create an abstract PaymentMethod class with an abstract pay(amount) method. Implement CardPayment and UpiPayment. Show that PaymentMethod() itself cannot be created, then process a payment with each method.

    Show hint

    Inherit from ABC and decorate the method with @abstractmethod. Creating an instance of a class with unimplemented abstract methods raises TypeError.

    Show solution
    Python
    from abc import ABC, abstractmethod
    
    
    class PaymentMethod(ABC):
        @abstractmethod
        def pay(self, amount):
            ...
    
    
    class CardPayment(PaymentMethod):
        def pay(self, amount):
            return f"Paid {amount} by card"
    
    
    class UpiPayment(PaymentMethod):
        def pay(self, amount):
            return f"Paid {amount} by UPI"
    
    
    try:
        PaymentMethod()
    except TypeError:
        print("PaymentMethod is abstract and cannot be created")
    
    for method in [CardPayment(), UpiPayment()]:
        print(method.pay(499))
    Output
    PaymentMethod is abstract and cannot be created
    Paid 499 by card
    Paid 499 by UPI

    The abstract class defines what every payment method must do, and each subclass decides how. Code that receives any PaymentMethod can call pay() and trust that it exists.

  8. 8.When are two objects equal?

    Intermediate

    Write a Point class with x and y. Show that two points with the same coordinates are not equal by default, then add __eq__ so they are, and show the difference between == and is.

    Show hint

    By default, == on objects compares identity. Define __eq__(self, other) to compare the coordinates.

    Show solution
    Python
    class PlainPoint:
        def __init__(self, x, y):
            self.x, self.y = x, y
    
    
    class Point:
        def __init__(self, x, y):
            self.x, self.y = x, y
    
        def __eq__(self, other):
            if not isinstance(other, Point):
                return NotImplemented
            return (self.x, self.y) == (other.x, other.y)
    
    
    print(PlainPoint(1, 2) == PlainPoint(1, 2))
    
    a = Point(1, 2)
    b = Point(1, 2)
    print(a == b)
    print(a is b)
    Output
    False
    True
    False

    == asks "do these have the same value?" (using __eq__), while is asks "are these the very same object?". Two separately created points are equal but not identical. The @dataclass decorator generates __init__, __repr__ and __eq__ for you.