Inheritance and polymorphism: reuse a class and vary its behaviour
Build on classes, objects and encapsulation by following what happens when different kinds of ticket respond to the same method call.
Content owner: Michael Print · Written for A-Level learners · Checked against official specifications
The idea to start with
Inheritance defines a specialised class using an existing class. The subclass can reuse attributes and methods and override a method when it needs a different implementation. An appropriate relationship means the specialised object can still fulfil the general class's role.
Polymorphism lets a caller use a shared operation on different kinds of object while each object supplies its appropriate behaviour. In the example, every ticket supports cost_pence(), so the checkout loop does not need separate pricing branches for each ticket type.
OCR H446 · 1.2.4(e), 2.2.1(f)
Before you start
Useful foundations
Classes, objects, attributes and methods
Functions, parameters and return values
Integer arithmetic in Python
By the end, you should be able to
Identify superclass and subclass relationships.
Trace inherited and overridden methods.
Explain the purpose of super in this single-inheritance example.
Demonstrate polymorphism with one shared calling interface.
Design the relationship before writing the syntax
A science exhibition sells standard and discounted student tickets. Each has a holder, base price and cost operation. Checkout expects that shared interface.
A shared field alone does not justify inheritance. Use it when the specialised object can fulfil the general role. Use composition when an object contains or uses another.
The example uses integer pence and truncates fractional-penny discounts through integer division. This is a stated pricing rule; a real system must define its own rounding policy.
Is-a versus has-a
StudentTicket is a Ticket
Use Ticket as superclass and StudentTicket as subclass. Either can fulfil the ticket cost contract.
Booking has tickets
Store ticket objects inside a booking. A booking is not one ticket.
Reuse initialisation and override the varying operation
class StudentTicket(Ticket) declares inheritance. Its constructor calls super().__init__(holder, base_pence) for common fields, then stores the discount percentage. This single-inheritance example reaches the superclass; more complex hierarchies are outside its scope.
Ticket.cost_pence returns base price. The subclass overrides that method with the same name and caller-facing arguments to apply a discount. Without an override, it would inherit the base implementation.
super().cost_pence() reuses the base method on the same object. It does not turn the object into a superclass object. Calling self.cost_pence() inside the override instead would call itself repeatedly.
Observe polymorphism in the caller
Checkout calls one operation on every ticket. The appropriate implementation supplies the cost without an “if student” pricing branch. Another class meeting that contract can leave the loop unchanged.
Python can also use unrelated objects sharing suitable operations. Here inheritance makes reuse explicit. Polymorphism means the same call supports differing appropriate behaviours, rather than merely two class names.
A single underscore marks a Python non-public convention, without enforcing privacy. Methods and constructors protect invariants here: base price cannot be negative and discount must be 0…100.
The same call uses the override: a 25% discount on 1000 gives 750 pence.
StudentTicket for Cy→
The same call uses the override: a 50% discount on 800 gives 400 pence.
Checkout uses the shared operation; the object’s class determines its behaviour.
Worked example
Run the original exhibition checkout
Create a Ticket for Ada with a base price of 1000 pence. Its cost method returns 1000.
Create a StudentTicket for Ben with the same base and a 25 percent discount. The superclass sets common attributes, and the override calculates 1000 × 75 // 100 = 750.
Create a StudentTicket for Cy with an 800-pence base and a 50 percent discount. The same method call calculates 400.
The loop prints each holder and cost, then the total 2150. The displayed output comes from these exact objects; changing the order of objects changes only the output order, not their costs.
Exact output from ticket.py
Line
Output
1
Ada 1000
2
Ben 750
3
Cy 400
4
Total 2150
ticket.py — Python 3, original inheritance examplePython
class Ticket:
def __init__(self, holder, base_pence):
if base_pence < 0:
raise ValueError("negative price")
self.holder = holder
self._base_pence = base_pence
def cost_pence(self):
return self._base_pence
class StudentTicket(Ticket):
def __init__(self, holder, base_pence, discount_percent):
super().__init__(holder, base_pence)
if not 0 <= discount_percent <= 100:
raise ValueError("discount outside 0..100")
self._discount_percent = discount_percent
def cost_pence(self):
base = super().cost_pence()
return base * (100 - self._discount_percent) // 100
tickets = [Ticket("Ada", 1000),
StudentTicket("Ben", 1000, 25),
StudentTicket("Cy", 800, 50)]
for ticket in tickets:
print(ticket.holder, ticket.cost_pence())
print("Total", sum(ticket.cost_pence() for ticket in tickets))
Worked example
Check boundaries before claiming the class works
Under the integer-input assumptions, a zero percent discount gives the base price and a 100 percent discount gives zero. These are valid boundary percentages.
A negative base price and a discount of 101 must each raise ValueError. Creating a standard ticket does not need a student-discount field.
Make two StudentTicket objects with different percentages. Their instance state is independent; creating the second must not alter the first's cost.
When extending the hierarchy, preserve the contract that cost_pence returns a non-negative integer cost. A subclass returning a message instead would break callers such as sum even though its method has the right name.
Original A-Level practice
5 original questions total 19 marks. Attempt each before opening the independently written indicative marking guidance.
Question 1
4 marks
Identify the superclass and subclass in the example. Explain one reused feature and one specialised feature.
Show solution and marking guidance+
Indicative answer
Ticket is the superclass (1) and StudentTicket is the subclass (1). Common holder/base-price initialisation is reused (1), while cost_pence is overridden to apply a discount (1).
Question 2
4 marks
A StudentTicket has a base price of 1200 pence and a 30 percent discount. Give its cost and explain the two cost_pence implementations involved.
Show solution and marking guidance+
Indicative answer
The object invokes the StudentTicket override (1). super().cost_pence() obtains the base 1200 from Ticket (1). The override multiplies by 70 and integer-divides by 100 (1), returning 840 pence (1).
Question 3
4 marks
Explain where the runnable program demonstrates polymorphism and why the checkout can work with another suitable ticket subclass.
Show solution and marking guidance+
Indicative answer
The loop uses the same ticket.cost_pence() call for different ticket types (1). The appropriate class implementation is chosen for each object (1). A new subclass providing the same non-negative integer cost contract can be called the same way (1), so the checkout need not add a type-specific pricing branch (1).
Question 4
3 marks
A Booking stores several tickets and a confirmation code. Decide whether it should inherit from Ticket or contain ticket objects, giving two reasons.
Show solution and marking guidance+
Indicative answer
Contain ticket objects through composition (1). A booking has tickets rather than being one ticket (1), and its multiple-ticket responsibilities/confirmation code differ from the single-ticket cost interface (1).
Question 5
4 marks
Give expected outcomes for a 0 percent discount, a 100 percent discount, a 101 percent discount and a negative base price. All other values are valid integers.
Show solution and marking guidance+
Indicative answer
Zero percent returns the base price (1). One hundred percent returns zero (1). A discount of 101 raises ValueError (1), and a negative base price raises ValueError (1).
Specification and references
This guide addresses OCR H446 1.2.4(e), 2.2.1(f). Check your examination year and the complete specification for the assessment scope.
These are independently written explanations and practice questions. CompSciTutoring.co.uk is not affiliated with or endorsed by an examination board. The marking guidance is indicative; always check the syllabus for your examination year.