← All lessons · Cambridge 9618 A Level · Unit 20.1 · Paper 4 Practical
Work through this page, then give the club a junior member class that inherits from Member, and submit the Evidence Document on Teams.
Start here
Last week you learned to tell has a from is a. This week is the is a side: a child class that is everything its parent is, plus a little more.
Member class does not change at all. You will write a new class, JuniorMember, that inherits from it. If your Member class is not working, there is a version in the task section to start from.The two traps this lesson is about
1. Not calling the parent's constructor. A child class must pass the parent's parameters up to the parent's constructor. That call is a separate mark in almost every Paper 4 inheritance question, and in June 2025 examiners named the parent-constructor call a national weakness.
2. Reaching into the parent's private attributes. A child class inherits the parent's attributes, but its code cannot use them by name. In Python, self.__Name inside the child crashes. You go through the parent's methods instead — and once you see why, it stops being a surprise.
Retrieval · real past-paper questions
Questions 1 and 2 are real Cambridge Paper 3 questions. Write your answers in your book, then reveal the mark scheme and mark them — strictly.
Questions
Club lists the attribute Captain : Member. Write the class header for Club in Python. [1]| Q | Accepted (Cambridge mark scheme wording) | Marks |
|---|---|---|
| 1 | Encapsulation — putting properties and methods inside a class // ensures sensitive data is hidden from users by hiding values of a structured data object inside a class. Getter — a method that is used to return the value of a property. Setter — a method that is used to update the value of a property. Reject: "a getter gets the value" — it defines the word with itself. Examiners want returns. | 3 |
| 2 | Instance — an occurrence of an object // a specific object based on the class // an instantiation of a class. Inheritance — the capability of defining a new class of objects that has all the attributes and methods from a parent class. Reject: "when a class inherits from another class" — the word being defined cannot be the definition. | 2 |
| 3 | class Club: — nothing in brackets. Member is in the data type column, so a club has a captain: containment.Reject: class Club(Member): | 1 |
Q2 is the bridge into today. Notice the word in the mark scheme: a new class that has all the attributes and methods of its parent. That is a promise, and today you will see exactly how Python keeps it — and the one place where it surprises people.
Where this is going
| By the end of this lesson I can… | Syllabus | Paper |
|---|---|---|
| Declare a child class whose constructor takes the parent's parameters plus its own, and calls the parent's constructor. | 20.1 | 4 |
| Explain why a child class cannot use the parent's private attributes by name, and use the parent's methods instead. | 20.1 | 4 |
| Override a parent method, extending it by calling the parent's version. | 20.1 | 4 |
| Define inheritance and polymorphism, and complete a class inheritance diagram. | 20.1 | 3 |
Vocabulary
| Term | Definition that earns the mark |
|---|---|
| Inheritance | The capability of defining a new class that has all the attributes and methods of a parent class. The methods and properties of the parent (base) class are made available to, and reused by, the child (derived) class. |
| Parent constructor call super().__init__ | The first line of a child's constructor. It passes the parent's parameters to the parent's constructor, which sets the parent's attributes for this object. |
| Overriding | Writing a method in the child class with the same identifier as a method in the parent class. For objects of the child class, the child's version is used instead of the parent's. |
| Polymorphism | Allows the same method to take on different behaviours depending on which class is instantiated // methods can be redefined for derived classes. |
How definitions lose the mark
"Inheritance is when a class inherits from another class" scores nothing — it uses the word to define itself. Examiners have rejected exactly this (November 2022). Say what the child class gets: all the attributes and methods of the parent. And for polymorphism, "a method with many forms" is a translation of the Greek, not a definition. Say what happens: the same method behaves differently depending on the class of the object.
PRIMM · Step 1
Read this program carefully. Do not run it yet. Novel is a child of Book. Write down exactly what you think the program will output — every line.
class Book: def __init__(self, Title, Pages): self.__Title = Title # STRING self.__Pages = Pages # INTEGER def GetTitle(self): return self.__Title def Describe(self): print(self.__Title + ", " + str(self.__Pages) + " pages") class Novel(Book): def __init__(self, Title, Pages, Genre): super().__init__(Title, Pages) self.__Genre = Genre # STRING def Describe(self): super().Describe() print("Genre: " + self.__Genre) Books = [Book("Atlas of the World", 320), Novel("Dune", 412, "Science fiction")] for Item in Books: Item.Describe() print(Books[1].GetTitle())
Commit your prediction before you scroll
Two harder questions: Novel has no GetTitle method — so what happens on the last line? And Item.Describe() is the same line of code both times round the loop — will it print the same thing both times?
PRIMM · Step 2
Now run it and compare the real output with your prediction.
Press Run to execute.
Your code and your prediction are saved in this browser, so closing the tab does not lose them. If a program never finishes, press Stop.
Atlas of the World, 320 pages Dune, 412 pages Genre: Science fiction Dune
Book, Item.Describe() runs Book's version — one line. For the Novel, it runs Novel's own version, which first calls the parent's version and then adds the genre — two lines. Python picks the version that belongs to the object's own class. That is polymorphism.Novel never defines GetTitle. A novel is a book, so it has every method a book has. Python looks in Novel first, does not find it, and uses Book's.Book's constructor, which Novel's constructor calls on its first line. Nothing in Novel mentions __Title at all.Try this — then read why it matters
In Novel's constructor, delete the line super().__init__(Title, Pages) and run it again.
The atlas still prints, then it crashes on Dune: AttributeError: 'Novel' object has no attribute '_Book__Title'. Without that line the parent's constructor never runs for the novel, so the novel never gets a title or a page count — and the first time a parent method needs them, there is nothing there. That one line is the mark examiners keep finding missing. Put it back before you go on.
PRIMM · Step 3
class Novel(Book): # 1. the parent goes in the brackets def __init__(self, Title, Pages, Genre): # 2. the parent's parameters, then its own super().__init__(Title, Pages) # 3. the parent's constructor sets Title and Pages self.__Genre = Genre # 4. only the child's own attribute — STRING
class Novel(Book): — a novel is a book.Book needs, plus what a Novel adds. Here that is Title and Pages for the parent, and Genre for the child. Follow the order the design gives.super().__init__(Title, Pages) runs Book's constructor for this object. Book.__init__(self, Title, Pages) is also accepted — note that this form needs self.Title or Pages in the child. The parent already has them, and copies made in the child are different attributes that the parent's methods cannot see.| Python (Paper 4) | Cambridge pseudocode (Paper 3) | |
|---|---|---|
| Class header | class Novel(Book): | CLASS Novel INHERITS Book |
| Parent constructor call | super().__init__(Title, Pages) | SUPER.NEW(Title, Pages) |
The parent class Car has the constructor __init__(self, Make, Model, Price). The design says: "ElectricCar is a subclass of Car. Its constructor takes the make, model and price, and the battery range, BatteryRange : INTEGER." Which constructor follows the design?
TypeError: Car.__init__() missing 3 required positional arguments.ElectricCar, not to Car. The first time a Car method such as GetMake() is used on an electric car, it crashes: 'ElectricCar' object has no attribute '_Car__Make'.Car's constructor takes three values, not four, so Python stops: TypeError: Car.__init__() takes 4 positional arguments but 5 were given (it counts self). The battery range belongs to the child, so the child stores it itself.Every Novel object really does hold a title: the parent's constructor put it there. But the title is private to Book, the class that declared it. Code written inside Novel is not code written inside Book, so it has to ask, exactly as the main program does.
| Inside a Novel method you write | What happens |
|---|---|
| self.GetTitle() | ✓ Returns the title. GetTitle is inherited from Book, and Book's own code is allowed to read __Title. |
| self.__Title | ✗ AttributeError: 'Novel' object has no attribute '_Novel__Title'. Python renames a private attribute after the class it is written in, so inside Novel it looks for a Novel title — and there isn't one. |
Do not take Python's hint
Python sometimes adds Did you mean: '_Book__Title'? to that error. Typing self._Book__Title would work — and it is reaching round the privacy that earned you the attribute marks in the first place. This is not a Python quirk, either: Private in VB.NET and private in Java hide the attribute from the child class in just the same way. The child uses the parent's methods.
You add a method to Novel that outputs, for example, Dune is a novel. Which line works and respects the design?
Novel, Python reads self.__Title as self._Novel__Title. The title was stored by Book as _Book__Title, so this crashes with an AttributeError. Private means private to the class that declared it.super() gives you the parent's methods, not its private attributes. Python still renames __Title to _Novel__Title and crashes: 'super' object has no attribute '_Novel__Title'.GetTitle is inherited from Book, so every novel has it, and it is Book's own code that reads the private attribute. The child goes through the parent's methods, not round them.Book for a title, not this particular novel — and the class itself has no title, only its objects do. It also hits the private renaming, so it crashes with an AttributeError.A child overrides a method by defining one with the same identifier. There are two ways to write the new version, and Cambridge credits both — the mark schemes accept "overriding / overloading / extending / using the parent method".
| Way | Looks like | Use it when |
|---|---|---|
| Extend | def Describe(self): super().Describe() print("Genre: " + self.__Genre) | The child does everything the parent does, and more. Almost always the right choice in Paper 4 — and it reuses the parent's output without retyping it. |
| Replace | def Describe(self): print("A novel: " + self.GetTitle()) | The child behaves differently and does not need the parent's version at all. |
Polymorphism is what you saw in the loop: one call, Item.Describe(), and Python runs the version that belongs to the object's own class. Python and Java need no keyword to override. VB.NET does — Overridable on the parent's method and Overrides on the child's.
The class Mix is a child of Yarn. It overrides YarnInfo() like this:
def YarnInfo(self): # inside class Mix super().YarnInfo() print("Acrylic: " + str(self.__Percentage) + "%")
The main program then runs Plain = Yarn("Plain", "Red", 3) and Plain.YarnInfo(). Plain is a Yarn, not a Mix. What is output?
Plain is a Yarn, so it runs Yarn's method — and a plain yarn does not even have a __Percentage.Yarn object still uses Yarn's own method. That is polymorphism working in both directions.Yarn still has its own YarnInfo; Mix simply has a different one for its own objects.super() is only how a child reaches the parent's version from inside its own override.Paper 3 asks you to design inheritance rather than code it. This is the yarn shop from June 2022, which Checkpoint 3 borrowed. Read it and complete the diagram in the box below before you open the mark scheme.
9618 Paper 3, June 2022 (variant 1), Q7(a) [5]
A program is to be written using Object-Oriented Programming (OOP) for a shop that sells knitting yarn. There are three types of yarn: acrylic, wool or mix. The following data are stored for each type: name; colour; batch code; weight; number of balls of yarn in stock (can be edited); type of yarn.
The following statements apply to yarn: acrylic can be soft or not soft; wool can be lamb, merino or alpaca; mix contains a percentage of acrylic. Each type of yarn has a method that will display all the information about the yarn.
Complete this class inheritance diagram to show the properties, methods and inheritance.
On paper you would also draw the inheritance arrows. Say in your answer which way they point.
Your answer — commit it before you open the mark scheme
| Point | One mark per point |
|---|---|
| 1 | Acrylic has attribute Soft of type BOOLEAN. |
| 2 | Wool has attribute WoolType with a suitable data type (STRING). |
| 3 | Acrylic and Wool have the method YarnInfo(). |
| 4 | Acrylic, Wool and Mix: at least one inherits from Yarn (one arrow correct)… |
| 5 | …all three inherit from Yarn (all arrows correct). |
YarnInfo(). Mix was given to you as the pattern.Name, Colour and the rest in the children. They are inherited — exactly as in Python, where the child never re-declares the parent's attributes.… at the end of mark 4 and the start of mark 5: in Cambridge mark schemes that means mark 5 cannot be awarded without mark 4.PRIMM · Step 4
Go back to the Predict program in the runner. Make these changes one at a time, running after each.
Changes to make
IsLong() to Novel. It returns True if the book has more than 400 pages, and False otherwise. Try writing it with self.__Pages first, and read the error. Then fix it properly: give Book a get method GetPages(), and call that.Textbook, which inherits from Book and has one extra attribute, Subject : STRING, taken from a parameter. It overrides Describe() so that it outputs everything a book outputs, then Subject: followed by the subject.Textbook("Cambridge A Level CS", 560, "Computer Science") and Novel("Holes", 233, "Adventure") to the end of the list. Replace the last line with two lines that output IsLong() for Dune and for Holes.class Book: def __init__(self, Title, Pages): self.__Title = Title # STRING self.__Pages = Pages # INTEGER def GetTitle(self): return self.__Title def GetPages(self): # new: the child needs it return self.__Pages def Describe(self): print(self.__Title + ", " + str(self.__Pages) + " pages") class Novel(Book): def __init__(self, Title, Pages, Genre): super().__init__(Title, Pages) self.__Genre = Genre # STRING def Describe(self): super().Describe() print("Genre: " + self.__Genre) def IsLong(self): if self.GetPages() > 400: return True else: return False class Textbook(Book): def __init__(self, Title, Pages, Subject): super().__init__(Title, Pages) self.__Subject = Subject # STRING def Describe(self): super().Describe() print("Subject: " + self.__Subject) Books = [Book("Atlas of the World", 320), Novel("Dune", 412, "Science fiction"), Textbook("Cambridge A Level CS", 560, "Computer Science"), Novel("Holes", 233, "Adventure")] for Item in Books: Item.Describe() print(Books[1].IsLong()) print(Books[3].IsLong())
Expected output:
Atlas of the World, 320 pages Dune, 412 pages Genre: Science fiction Cambridge A Level CS, 560 pages Subject: Computer Science Holes, 233 pages Genre: Adventure True False
'Novel' object has no attribute '_Novel__Pages' — the private renaming from section 2. The fix is a get method in the parent, used by the child. In the exam the design usually gives you the get methods you need; if a child method needs parent data, look for one.return self.GetPages() > 400 on one line is equally correct. The if version makes it obvious that there is a return on every path.Describe. That is why polymorphism matters: code that works with a parent class keeps working when new children arrive.The marks
A Paper 4 version of the yarn shop. The parent class is given; you write the child.
Class design
| Yarn — the parent | Description |
|---|---|
| Name : STRING Colour : STRING NumberBalls : INTEGER | All three set from the constructor's parameters, in that order. |
| EditNumberBalls(Change) | Adds Change to NumberBalls. |
| YarnInfo() | Outputs the name, colour and number of balls. |
| Mix — a subclass of Yarn | Description |
|---|---|
| Percentage : INTEGER | From a parameter, after the parent's three. |
| YarnInfo() | Overrides the parent method. Outputs all the information that Yarn outputs, then Acrylic: , the percentage and %. |
Predict, then reveal. A candidate writes this. Decide what an examiner would credit before you open the answer.
Candidate responseclass Mix(Yarn): def __init__(self, Name, Colour, NumberBalls, Percentage): self.__Name = Name self.__Colour = Colour self.__NumberBalls = NumberBalls self.__Percentage = Percentage # INTEGER def YarnInfo(self): print("Name: " + self.__Name) print("Colour: " + self.__Colour) print("Acrylic: " + self.__Percentage + "%")
class Mix(Yarn): — right relationship, right direction.Mix — _Mix__Name and so on. Yarn's attributes are never set, so the first time a parent method runs on a mix — EditNumberBalls(5), say — it crashes with 'Mix' object has no attribute '_Yarn__NumberBalls'.Percentage is private and its type is evidenced.Yarn outputs"."Acrylic: " + self.__Percentage joins a string to an integer — TypeError after two lines of output. In Python, build strings with + and str().What would have scored
class Mix(Yarn): def __init__(self, Name, Colour, NumberBalls, Percentage): super().__init__(Name, Colour, NumberBalls) self.__Percentage = Percentage # INTEGER def YarnInfo(self): super().YarnInfo() print("Acrylic: " + str(self.__Percentage) + "%")
The habit to build: the moment you type a child's constructor header, the very next line is super().__init__( with the parent's parameters. And when a design says the child outputs "all the information the parent outputs, then…", the override's first line is super(). plus the method's name. Shorter, safer, and it reaches data the child is not allowed to touch.
Assessed work
Everyone starts at Independent. Drop to Scaffolded if you stall. Go to Exam Conditions when you have finished.
The scenario
The sports club now takes members under 14. A junior member is a member — they have everything a Member has — but they also need a guardian, and they cannot earn credits until their guardian has given consent. The class Member is unchanged. A new class, JuniorMember, inherits from Member and has the following design.
| Attribute | Data type | Set when the object is created |
|---|---|---|
| GuardianName | STRING | From a parameter |
| ConsentGiven | BOOLEAN | Always FALSE |
| Method | Description |
|---|---|
| Constructor | Takes the member ID, name and age, then the guardian's name. Calls the parent constructor, then initialises the two new attributes as described above. |
| SetConsentGiven(NewConsent) | Assigns NewConsent to ConsentGiven. |
| DisplayDetails() | Overrides the parent method. Outputs all the details that Member outputs, then "Guardian : " followed by the guardian's name and "Consent : " followed by ConsentGiven. |
| AddCredits(Amount) | Overrides the parent method. If consent has not been given, outputs the member's name followed by " needs consent before earning credits" and adds nothing. Otherwise adds Amount exactly as the parent method does, including the limit of 50. |
Test data — use it in this order:
| # | Step |
|---|---|
| 1 | Create a junior member: "J5101", "Ava Brooks", 13, guardian "Helen Brooks" |
| 2 | Add 25 credits |
| 3 | Set consent to TRUE |
| 4 | Add 10 credits, then add 30 credits |
| 5 | Display the member's details |
Copy this into your editor and build JuniorMember underneath it. Do not change it — the whole point is that the child class adds to it without editing it.
class Member: def __init__(self, MemberID, Name, Age): self.__MemberID = MemberID # STRING self.__Name = Name # STRING self.__Age = Age # INTEGER self.__Credits = 0 # INTEGER self.__Active = True # BOOLEAN def DisplayDetails(self): print("Member ID : " + self.__MemberID) print("Name : " + self.__Name) print("Age : " + str(self.__Age)) print("Credits : " + str(self.__Credits)) print("Active : " + str(self.__Active)) def GetName(self): return self.__Name def GetCredits(self): return self.__Credits def SetAge(self, NewAge): self.__Age = NewAge def AddCredits(self, Amount): NewTotal = self.__Credits + Amount if NewTotal > 50: NewTotal = 50 self.__Credits = NewTotal
(a) Write program code to declare the class JuniorMember and its constructor. [5]
(b) Write program code for the method SetConsentGiven(). [1]
(c) Write program code for the method DisplayDetails() in JuniorMember. [3]
(d) Write program code for the method AddCredits() in JuniorMember. [3]
(e) Write a main program that carries out the five steps of the test data, in order, on one JuniorMember object. [2]
(f) Take a screenshot showing the program running. It must show the test data and the complete output. [2]
Same questions, same marks. Put this below your Member class and complete every ______ and # ....
class JuniorMember(______): # a junior member IS A member def __init__(self, MemberID, Name, Age, ______): super().__init__(______, ______, ______) # the parent's three parameters self.__GuardianName = ______ # STRING # ... ConsentGiven (fixed value — not a parameter) # BOOLEAN def SetConsentGiven(self, NewConsent): # ... attribute on the left, parameter on the right def DisplayDetails(self): super().______() # everything a Member outputs print("Guardian : " + ______) print("Consent : " + str(______)) def AddCredits(self, Amount): if self.__ConsentGiven == ______: print(self.______() + " needs consent before earning credits") else: # ... let the parent's AddCredits do the adding (and the limit) # ---- main program ---- Junior = JuniorMember("J5101", "Ava Brooks", 13, "Helen Brooks") Junior.AddCredits(25) # ... set consent to True # ... add 10 credits, then 30 credits # ... display the details
Why self.GetName() and not self.__Name? Section 2 of Investigate.
Close this page. Set a timer for 20 minutes and work from the scenario box alone. Then, as an extension, put a junior member into last week's club — and watch the club cope without a single change.
| # | Extension steps — add to the end of your main program |
|---|---|
| 1 | Add your Lesson 4 Club class to the program, unchanged. |
| 2 | Create a club "Riverside Juniors". Create an ordinary Member "M4021", "Sara Ahmed", 16, and add 20 credits. |
| 3 | Add Sara, then your junior member Ava, to the club with AddMember(). |
| 4 | Output the total credits with the label Total credits: , then call DisplayMember("Ava Brooks"). |
Then answer in one sentence, in the Evidence Document: the Club class was not changed, so why does it output Ava's guardian?
Before you screenshot
Ava Brooks needs consent before earning credits, then seven lines of details ending with Consent : True.AddCredits is not checking consent — the 25 was added too, and the limit hid it.AttributeError: ... '_Member__Name' (or _Member__ anything) means the parent's constructor never ran. Check the super().__init__ line.AttributeError: ... '_JuniorMember__Name' (or _JuniorMember__Credits) means the child is reaching into the parent's private attributes. Use GetName(), and let super().AddCredits(Amount) do the adding.DisplayDetails replaces the parent's instead of extending it.Self-marking
Mark your own work honestly against this, then record your total in the Evidence Document.
| Point | Description | Marks |
|---|---|---|
| (a) MP1 | Class header shows inheritance from Member — class JuniorMember(Member):. | 1 |
| (a) MP2 | Constructor takes four parameters after self: the parent's three, then GuardianName. | 1 |
| (a) MP3 | Calls the parent's constructor, passing the member ID, name and age. | 1 |
| (a) MP4 | GuardianName private, type evidenced, assigned from the parameter. | 1 |
| (a) MP5 | ConsentGiven private, type evidenced, assigned False inside the constructor — not taken as a parameter. | 1 |
| (b) MP6 | SetConsentGiven() takes one parameter and assigns it to ConsentGiven — attribute on the left. | 1 |
| (c) MP7 | A method DisplayDetails() defined in JuniorMember, with no extra parameters — it overrides the parent's. | 1 |
| (c) MP8 | …calls the parent's DisplayDetails(), so all five of the member's details are output. | 1 |
| (c) MP9 | Outputs the guardian's name and the consent value with their labels, using str() for the Boolean. | 1 |
| (d) MP10 | A method AddCredits() defined in JuniorMember, with one parameter — it overrides the parent's. | 1 |
| (d) MP11 | Tests ConsentGiven; when it is False, outputs the message using GetName() and adds nothing. | 1 |
| (d) MP12 | Otherwise calls the parent's AddCredits(Amount), so the limit of 50 still applies. | 1 |
| (e) MP13 | A JuniorMember object created with the test data, in a variable not named after the class. | 1 |
| (e) MP14 | All five steps called on that object, in order, through its methods. | 1 |
| (f) MP15 | Screenshot shows the test data being used, as well as the output. | 1 |
| (f) MP16 | Output shows the consent message first, then the details with Credits : 40, Guardian : Helen Brooks and Consent : True. | 1 |
Accepted alternatives
Member.__init__(self, MemberID, Name, Age) in place of super().__init__(MemberID, Name, Age).Member.DisplayDetails(self) and Member.AddCredits(self, Amount).if not self.__ConsentGiven:, or testing for True first and putting the message in the else.Not credited, however good the code looks
MemberID, Name, Age, Credits or Active in the child — MP3 lost. The parent already has them.ConsentGiven taken as a constructor parameter — MP2 and MP5 lost. The design fixes it at FALSE.print lines in the child's DisplayDetails() — MP8 lost, and it crashes on self.__MemberID anyway.self.__Name or self.__Credits anywhere in JuniorMember — MP11 or MP12 lost. Private to Member.Member class to add consent — no marks for (c) and (d). The design asks for a child class that overrides; the parent is not yours to change.Extension — not counted in the 16
Club class used without any change.AddMember(); the output reads Total credits: 60.DisplayMember("Ava Brooks") outputs all seven of Ava's detail lines, including the guardian and consent.Club calls DisplayDetails() on each object, and because Ava is a JuniorMember, her class's overriding version runs.Hand-in
One file: the Evidence Document for this lesson, saved as .docx and named Surname Initial — OOP Home Lesson 5.docx.
Checklist before you upload
super().__init__(MemberID, Name, Age)..py file.Your next taught lesson opens by checking these submissions. If you got stuck, submit what you have with a note saying where.
Home Lesson 6 — A Full Paper 4 Question. Everything from the last five weeks in one exam-style question: a class, a child class, a container, and a main program — against the clock, then marked and evidenced exactly as in June.