Paper 2 OOP Answers

These answers correspond to Paper 2 OOP Drills.

Verification note: every Python code block in this answer file has been executed locally.

Answer 1: Define Class

Model answer:

class Book:
    def __init__(self, title, copies):
        self.title = title
        self.copies = copies
 
    def available(self):
        return self.copies > 0
 
 
book = Book("Algorithms", 3)
print(book.title)
print(book.available())

Expected output:

Algorithms
True

Mark points:

  • defines class Book;
  • defines __init__(self, title, copies);
  • stores title as an instance attribute using self;
  • stores copies as an instance attribute using self;
  • defines available(self);
  • uses the correct condition copies > 0;
  • returns the correct Boolean result.

Common weak answer:

  • assigning title = title without self, which creates only a local variable.

Answer 2: Create Objects

Model answer:

class Book:
    def __init__(self, title, copies):
        self.title = title
        self.copies = copies
 
 
b1 = Book("Algorithms", 3)
b2 = Book("Networks", 0)
b3 = Book("Databases", 2)
 
print(b1.title)
print(b2.title)
print(b3.title)

Expected output:

Algorithms
Networks
Databases

Mark points:

  • creates first object with correct values;
  • creates second object with correct values;
  • creates third object with correct values;
  • stores them in separate variables or equivalent references;
  • prints all three titles in the required order.

Common weak answer:

  • reusing one variable and then trying to print earlier objects after their reference has been overwritten.

Answer 3: Getter Setter

Model answer:

class Student:
    def __init__(self, name, mark):
        self.name = name
        self._mark = mark
 
    def get_mark(self):
        return self._mark
 
    def set_mark(self, mark):
        if mark >= 0 and mark <= 100:
            self._mark = mark
            return True
        return False
 
 
s = Student("Amy", 60)
print(s.get_mark())
print(s.set_mark(75))
print(s.get_mark())
print(s.set_mark(120))
print(s.get_mark())

Expected output:

60
True
75
False
75

Mark points:

  • stores _mark as an instance attribute;
  • defines get_mark;
  • defines set_mark;
  • checks that the mark is within 0 to 100 inclusive;
  • updates only when valid;
  • returns a Boolean status.

Common weak answer:

  • setting the mark before validation, so invalid input changes the object state.

Answer 4: Inheritance

Model answer:

class Person:
    def __init__(self, name, email):
        self.name = name
        self.email = email
 
 
class Student(Person):
    def __init__(self, name, email, class_name):
        super().__init__(name, email)
        self.class_name = class_name
 
    def summary(self):
        return self.name + " " + self.class_name + " " + self.email
 
 
s = Student("Amy", "amy@example.com", "24S1")
print(s.summary())

Expected output:

Amy 24S1 amy@example.com

Mark points:

  • defines superclass Person;
  • stores shared name;
  • stores shared email;
  • defines subclass Student;
  • uses inheritance syntax;
  • calls super().__init__ or otherwise correctly initializes superclass attributes;
  • stores class_name;
  • returns the expected summary.

Common weak answer:

  • defining Student separately without inheriting from Person.

Answer 5: Override Method

Model answer:

class Notification:
    def send(self):
        return "generic"
 
 
class EmailNotification(Notification):
    def send(self):
        return "email sent"
 
 
class SMSNotification(Notification):
    def send(self):
        return "sms sent"
 
 
items = [EmailNotification(), SMSNotification()]
for item in items:
    print(item.send())

Expected output:

email sent
sms sent

Mark points:

  • defines superclass method;
  • defines EmailNotification as a subclass;
  • overrides send in EmailNotification;
  • defines SMSNotification as a subclass;
  • overrides send in SMSNotification;
  • creates subclass objects;
  • calls the same method name on each object;
  • produces different outputs.

Common weak answer:

  • giving the subclasses different method names, such as send_email and send_sms, which does not demonstrate the same method call.

Answer 6: Object List

Model answer:

class Book:
    def __init__(self, title, copies):
        self.title = title
        self.copies = copies
 
 
def available_titles(books):
    titles = []
    for book in books:
        if book.copies > 0:
            titles.append(book.title)
    return titles
 
 
books = [Book("Algorithms", 3), Book("Networks", 0), Book("Databases", 2)]
print(available_titles(books))

Expected output:

['Algorithms', 'Databases']

Mark points:

  • stores objects in a list;
  • loops through the object list;
  • accesses each object’s copies;
  • checks copies > 0;
  • accesses each matching object’s title;
  • appends matching titles;
  • returns the expected list.

Common weak answer:

  • filtering dictionaries or strings instead of the required objects.

Answer 7: Class Diagram to Code

Model answer:

class Device:
    count = 0
 
    def __init__(self, code, name):
        self.code = code
        self.name = name
        Device.count = Device.count + 1
 
    def label(self):
        return self.code + " " + self.name
 
 
d1 = Device("D01", "Laptop")
d2 = Device("D02", "Tablet")
print(d1.label())
print(d2.label())
print(Device.count)

Expected output:

D01 Laptop
D02 Tablet
2

Mark points:

  • defines class Device;
  • defines class attribute count;
  • defines __init__ with code and name;
  • stores code as an instance attribute;
  • stores name as an instance attribute;
  • updates Device.count through the class;
  • defines label;
  • returns the correct label string;
  • creates two Device objects with the required values;
  • produces all expected outputs.

Common weak answer:

  • writing self.count = self.count + 1. That can create or update an instance attribute instead of maintaining one shared class attribute.

Answer 8: Encapsulated Validation

Model answer:

class BankAccount:
    def __init__(self, balance):
        self._balance = balance
 
    def deposit(self, amount):
        if amount > 0:
            self._balance = self._balance + amount
            return True
        return False
 
    def get_balance(self):
        return self._balance
 
 
account = BankAccount(100)
print(account.deposit(50))
print(account.get_balance())
print(account.deposit(-20))
print(account.get_balance())

Expected output:

True
150
False
150

Mark points:

  • stores _balance;
  • defines deposit;
  • checks amount is positive;
  • updates balance for valid deposit;
  • rejects invalid deposit;
  • defines get_balance;
  • preserves balance after invalid input.

Common weak answer:

  • adding the amount before checking whether it is positive.

Answer 9: Implementation Independence

Model answer:

class Wallet:
    def __init__(self, opening_balance):
        self._transactions = [opening_balance]
 
    def deposit(self, amount):
        if amount > 0:
            self._transactions.append(amount)
            return True
        return False
 
    def withdraw(self, amount):
        if amount > 0 and amount <= self.get_balance():
            self._transactions.append(-amount)
            return True
        return False
 
    def get_balance(self):
        return sum(self._transactions)
 
 
wallet = Wallet(100)
print(wallet.get_balance())
print(wallet.deposit(40))
print(wallet.withdraw(30))
print(wallet.withdraw(200))
print(wallet.get_balance())

Expected output:

100
True
True
False
110

The calling code still uses deposit(amount), withdraw(amount), and get_balance(). This shows implementation independence: the internal representation changed to transaction records, but the public interface stayed the same.

Mark points:

  • initialises _transactions as a list containing the opening balance;
  • keeps the public method names deposit, withdraw, and get_balance;
  • accepts only positive deposits and appends accepted deposits;
  • accepts withdrawals only when positive and sufficient funds are available;
  • records accepted withdrawals as negative transaction amounts;
  • calculates the balance from the transaction list;
  • produces the expected output, including rejection of the invalid withdrawal.

Common weak answer:

  • continuing to store only one _balance number. The question requires a changed internal representation while preserving the public interface.

Answer 10: Display Method

Model answer:

class Item:
    def __init__(self, code, name, price):
        self.code = code
        self.name = name
        self.price = price
 
    def __str__(self):
        return self.code + " " + self.name + " $" + format(self.price, ".2f")
 
 
item = Item("P01", "Pen", 1.5)
print(item)

Expected output:

P01 Pen $1.50

Mark points:

  • defines class Item and its constructor;
  • stores all three instance attributes;
  • defines __str__(self);
  • formats price to two decimal places;
  • returns the exact required string.

Common weak answer:

  • returning the raw price with one decimal place, such as $1.5, when the required display is $1.50.