← All lessons · Cambridge 9618 A Level · Unit 20.1 · Paper 4 Practical
Work through this page, then build a whole club of members in Python and submit the Evidence Document on Teams.
Start here
Same routine. This page teaches, then makes you type — and this week your one Member becomes a whole club of them.
Member class does not change this week — everything new happens outside it. If your Lesson 2 class is not working, there is a version in the task section to start from.Why this lesson matters more than the last two
In the two most recent November examiner reports, Cambridge put this in its Key Messages: candidates can now declare a class well — the marks are being lost at the point where they have to create objects, store them, and work with them through their methods. That is this lesson. Lessons 1 and 2 built the class; this one uses it.
Retrieval
Answer these before you read anything else. Write them in your book, then reveal the answers and mark them.
Questions
def GetAge(self, Age): return self.__Age. Identify the error and write the corrected method header. [2]Amount to an attribute and never let it go above a limit. State the three steps, in the correct order. [1]range ends at 3 when the last index is 2. [2]
Scores = [12, 7, 15] Total = 0 for Index in range(0, 3): Total = Total + Scores[Index] print(Total)
| Q | Accepted | Marks |
|---|---|---|
| 1 | MP1 — a get method must not take a parameter (other than self).MP2 — def GetAge(self): | 2 |
| 2 | Add, then limit the new total, then store it. All three, in that order. | 1 |
| 3 | MP1 — 34MP2 — the end value of range is not included / range stops one before its end value, so it must be one greater than the last index. | 2 |
Q3 is the prerequisite check. Every loop in this lesson walks through an array by index. An off-by-one range either misses the last object or crashes past the end — examiners report it in seven of the last ten series.
Where this is going
| By the end of this lesson I can… | Syllabus | Paper |
|---|---|---|
| Declare an array of objects with its type evidenced, and store a new object in each element. | 20.1 | 4 |
| Call a method on one element of the array, and on every element using a loop. | 20.1 | 4 |
| Use the values that methods return to total, compare and search — comparing the right thing. | 20.1 | 4 |
Write a linear search over an array of objects that returns the index, or -1 only after every element has been checked. | 20.1 | 2 & 4 |
Vocabulary
| Term | Definition that earns the mark |
|---|---|
| Array of objects | An array whose elements are each an object of the same class. Its data type is that class — ARRAY[0:4] OF Member. |
| Element | One position in an array, identified by its index. |
| Index | The number that identifies an element's position. In Python the first index is 0. |
| Linear search | Checks each element in turn, from the first, until the item is found or every element has been checked. |
| Return value | The value a function or method passes back to the point where it was called. |
What will not earn the mark
"An array of objects is a list of classes" — no mark; the elements are objects (instances), not classes. "A linear search looks through the array" — incomplete; say it checks each element in turn and says when it stops.
PRIMM · Step 1
Read this program carefully. Do not run it yet. Write down exactly what you think the three print lines will output.
class Book: def __init__(self, Title, Pages): self.__Title = Title # STRING self.__Pages = Pages # INTEGER def GetTitle(self): return self.__Title def GetPages(self): return self.__Pages Shelf = [None] * 3 # ARRAY[0:2] OF Book Shelf[0] = Book("Dune", 412) Shelf[1] = Book("Emma", 474) Shelf[2] = Book("Holes", 233) print(Shelf[1].GetTitle()) Longest = Shelf[0] for Index in range(1, 3): if Shelf[Index].GetPages() > Longest.GetPages(): Longest = Shelf[Index] print(Longest.GetTitle() + " " + str(Longest.GetPages())) print(Shelf[2])
Commit your prediction before you scroll
Two harder questions: is Longest a number, a title, or something else? And why does the loop start at 1, not 0?
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.
Emma Emma 474 <__main__.Book object at 0x…>
Shelf[1].GetTitle() reads left to right: take the array, pick element 1, call its method. Element 1 is the second book, because indexes start at 0.Longest is a whole Book object, not a number and not a title. It starts as the first book and is replaced whenever a longer one turns up. Because it is an object, you can call its methods — Longest.GetTitle() — after the loop. The loop starts at 1 because element 0 is already in Longest; there is no point comparing it with itself.Display… method. Printing a whole object or list instead of the values inside it is a recurring output error in the examiner reports.Try this — then read why it matters
Add this line to the bottom and run it again:
print(Shelf.GetTitle())
It crashes: 'list' object has no attribute 'GetTitle'. Shelf is the whole array — a list of books — and a list does not have a GetTitle method. Only a book does. You must pick an element first, then call the method on it.
PRIMM · Step 3
Shelf = [None] * 3 # ARRAY[0:2] OF Book Shelf[0] = Book("Dune", 412)
[None] * 3 makes an array of three empty elements; each one then has a new object stored in it. The comment does the same job as last week's # STRING: Python cannot declare the type, so you show the examiner that this is an array of type Book. Building it with Shelf = [] and Shelf.append(Book(...)) works just as well — keep the comment either way.
| You write | What happens |
|---|---|
| Shelf[1].GetTitle() | ✓ Element 1's title is returned. |
| Shelf.GetTitle() | ✗ Crash — the list has no such method. |
| Shelf[3].GetTitle() | ✗ IndexError — a three-element array ends at index 2. |
| GetTitle(Shelf[1]) | ✗ Crash — a method is not a free-standing procedure; it is called on an object with a dot. |
for Index in range(0, 3): # pseudocode: FOR Index ← 0 TO 2 print(Shelf[Index].GetTitle())
The loop variable is the index, so Shelf[Index] is the object for this pass. The end of the range is one greater than the last index. When a question gives you FOR Index ← 0 TO 4, Python needs range(0, 5).
Almost every Paper 4 OOP question asks for one of these. Each one calls a get method on every element and does something with the value returned.
| Pattern | The shape of it |
|---|---|
| Total | Total = 0 before the loop; Total = Total + Shelf[Index].GetPages() inside it; use Total after it. |
| Count | Count = 0 before; if Shelf[Index].GetPages() > 400: Count = Count + 1 inside. |
| Best so far | Start with the first object; replace it whenever an element beats it — the Predict program. |
| Search | Compare each element's value with the target; return Index the moment it matches; return -1 only after the loop. |
Shelf[Index] == "Dune" never crashes and is never true: it compares a whole Book object with a string. You want Shelf[Index].GetTitle() == "Dune". Comparing the wrong thing — the index instead of the element, or the whole object instead of one field — is reported in six of the last ten series, and it is nasty because the program runs and simply never finds anything.
Shelf holds three Book objects. Which line outputs the title of the third book?
GetTitle method, and a get method takes no parameter anyway. Pick the element first: Shelf[2].Shelf[3] is past the end of a three-element array and crashes with an IndexError.print.Inside a loop, which condition is true when the book at Index is called Emma?
Book object with a string. It runs without an error and is never true, so the search silently finds nothing. Compare the value returned by a get method.(), even with no parameters.= is assignment, not comparison. Python stops with a SyntaxError. Comparison is ==.PRIMM · Step 4
Go back to the runner. Keep the class and the three books, and replace everything from print(Shelf[1]… downwards.
Changes to make
TotalPages() that returns the total number of pages of all the books in Shelf.FindBook(SearchTitle) that returns the index of the book with that title, or -1 if it is not on the shelf.Total pages: 1119 using TotalPages()."Holes" and output Holes is at index 2. Then search for "Beloved" and output Beloved is not on the shelf. Store what FindBook returns before you test it.def TotalPages(): Total = 0 for Index in range(0, 3): Total = Total + Shelf[Index].GetPages() return Total def FindBook(SearchTitle): for Index in range(0, 3): if Shelf[Index].GetTitle() == SearchTitle: return Index return -1 print("Total pages: " + str(TotalPages())) Position = FindBook("Holes") if Position == -1: print("Holes is not on the shelf") else: print("Holes is at index " + str(Position)) Position = FindBook("Beloved") if Position == -1: print("Beloved is not on the shelf") else: print("Beloved is at index " + str(Position))
Shelf because it is declared in the main program.FindBook, return -1 is lined up with the for, not the if. It runs only once the loop has checked every book.Position = FindBook(...) calls the search once and keeps the answer. Writing if FindBook("Holes") != -1: and then calling FindBook again inside the if does all the work twice — and when a call changes something, calling it twice is a real bug that examiners report in five series.The marks
With loops and returns, a line of code in the wrong place — inside the loop instead of before or after it — turns a correct idea into a wrong answer that still runs.
Predict, then reveal. The question says: "Write a function AveragePages() that returns the average number of pages of the three books in Shelf." A candidate writes this. It runs, and returns 137.33…. The right answer is 373.0. Find every fault before you open the answer.
def AveragePages(): for Index in range(0, 2): Total = 0 Total = Total + Shelf[Index].GetPages() return Total / 3
range(0, 2) misses the last book. It visits indexes 0 and 1 only. Three books need range(0, 3).Total = 0 is inside the loop. It is reset on every pass, so it can never hold more than one book's pages. Initialise once, before the loop.return is inside the loop. The function hands back its answer after the first book and stops — the loop never gets a second pass. That is why the answer is 412 ÷ 3.Shelf[Index].GetPages() — array, element, method, and the returned value is used. That line is credited.def AveragePages(): Total = 0 for Index in range(0, 3): Total = Total + Shelf[Index].GetPages() return Total / 3
The habit to build: for every line in a loop question, ask "once, or every time?" Setting up a total happens once — before. Adding to it happens every time — inside. Returning the answer happens once — after. The indentation of that line is your answer to the question.
Using the same three books (Dune, Emma, Holes), what does this function return for FindBook("Holes")?
def FindBook(SearchTitle): for Index in range(0, 3): if Shelf[Index].GetTitle() == SearchTitle: return Index else: return -1
Dune is not Holes, so the else runs and the function returns straight away. It never reaches index 2.Dune does not match, the else returns -1, and the function is over. This search can only ever find the first element. The fix: delete the else, and put return -1 after the loop, lined up with for.None only when it reaches the end without a return. Here the else guarantees a return on the very first pass.range(0, 3) only visits 0, 1 and 2, all of which exist — and in fact the loop never gets past index 0.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 stores all of its members in one array. The class Member is unchanged from Lesson 2 — constructor, DisplayDetails(), GetName(), GetCredits(), SetAge() and AddCredits(), with credits limited to 50.
| Identifier | Data type | Description |
|---|---|---|
| ClubMembers | ARRAY[0:4] OF Member | Stores the five members of the club. |
| Index | MemberID | Name | Age | Credits to add |
|---|---|---|---|---|
| 0 | "M4021" | "Sara Ahmed" | 16 | 20 |
| 1 | "M4022" | "Omar Khalid" | 17 | 45 |
| 2 | "M4023" | "Lina Haddad" | 16 | 60 |
| 3 | "M4024" | "Yusuf Rahman" | 18 | 0 |
| 4 | "M4025" | "Mia Chen" | 17 | 35 |
| Subroutine | Description |
|---|---|
| TotalCredits() | A function that returns the total credits of all the members in ClubMembers. |
| FindMember(SearchName) | A function that returns the index of the member whose name matches SearchName. If no member matches, it returns -1. |
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 array ClubMembers, store a Member object in each element using the test data, and add each member's credits using AddCredits(). [3]
(b) Write program code for the function TotalCredits(). [3]
(c) Write program code for the function FindMember(). [4]
(d) Write a main program that:
TotalCredits(), with a label"Lina Haddad" and, if she is found, calls DisplayDetails() on her object"Tom Reilly" and outputs Tom Reilly not found. [3](e) 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 # ....
def TotalCredits(): Total = ______ # once, before the loop for Index in range(______, ______): Total = Total + ClubMembers[Index].______() return ______ # once, after the loop def FindMember(SearchName): for Index in range(0, 5): if ClubMembers[Index].______() == SearchName: return ______ # ... what goes here, lined up with the for? ClubMembers = [None] * 5 # ARRAY[0:4] OF Member ClubMembers[0] = Member("M4021", "Sara Ahmed", 16) # ... elements 1 to 4 StartCredits = [20, 45, 60, 0, 35] for Index in range(0, 5): ClubMembers[Index].______(StartCredits[Index]) print("Total credits: " + str(______)) Position = FindMember("Lina Haddad") # ... if Position is not -1, call DisplayDetails on ClubMembers[Position] # ... then the same for "Tom Reilly", outputting "Tom Reilly not found"
Close this page. Set a timer for 25 minutes and work from the scenario box alone. Then, as an extension, write this function:
| Subroutine | Description |
|---|---|
| HighestCredits() | A function that returns the Member object with the most credits. |
At the end of your main program, call it once, store the object it returns, and use that object's get methods to output Most credits: Lina Haddad (50). Think carefully about what your "best so far" should start as.
Before you screenshot
AddCredits — if you see 160, your Lesson 2 limit is in the wrong place.FindMember cannot find Lina, check that you compare the name returned by GetName(), not the object — and that return -1 is not in an else.Self-marking
Mark your own work honestly against this, then record your total in the Evidence Document.
| Point | Description | Marks |
|---|---|---|
| (a) MP1 | ClubMembers declared with five elements, and its type evidenced as an array of Member (a comment is accepted). | 1 |
| (a) MP2 | A Member object created with the correct test data and stored in each of the five elements, in index order. | 1 |
| (a) MP3 | AddCredits() called on each element with that member's credits. | 1 |
| (b) MP4 | Total initialised to 0 before a loop that visits all five elements. | 1 |
| (b) MP5 | GetCredits() called on each element and the value returned added to the total. | 1 |
| (b) MP6 | Total returned, after the loop has finished. | 1 |
| (c) MP7 | Function takes SearchName as a parameter and loops through the elements. | 1 |
| (c) MP8 | …compares the value returned by GetName() for each element with SearchName — not the object itself. | 1 |
| (c) MP9 | Returns the index when a match is found. | 1 |
| (c) MP10 | Returns -1 only after every element has been checked. | 1 |
| (d) MP11 | TotalCredits() called and the value returned output with a label. | 1 |
| (d) MP12 | FindMember() called, its result stored, and then tested against -1. | 1 |
| (d) MP13 | DisplayDetails() called on the element at the returned index; not-found message output for Tom Reilly. | 1 |
| (e) MP14 | Screenshot shows the test data being used, as well as the output. | 1 |
| (e) MP15 | Output shows 150, Lina Haddad's details with 50 credits, and Tom Reilly not found. | 1 |
Accepted alternatives
ClubMembers = [] and append, as long as the type is evidenced and the order matches the indexes.AddCredits calls, or a loop over a second array of credit values.for Index in range(0, 5) or range(5). Looping with for M in ClubMembers: is accepted for (b), but (c) must return an index, so it needs one.Not credited, however good the code looks
range(0, 4) — MP4 lost; Mia is never counted.Total = 0 or return inside the loop — MP4 or MP6 lost.if ClubMembers[Index] == SearchName — MP8 lost, and nothing is ever found.return -1 in an else inside the loop — MP10 lost; only the first member can be found.print(ClubMembers[2]) instead of calling DisplayDetails() — MP13 lost.Extension — not counted in the 15
ClubMembers[0]), not as 0 or an empty value.GetCredits() of each remaining element with the best so far, and replaces it when greater.Most credits: Lina Haddad (50).Hand-in
One file: the Evidence Document for this lesson, saved as .docx and named Surname Initial — OOP Home Lesson 3.docx.
Checklist before you upload
return that ends a search or a total sits after the loop..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 4 — Containment or inheritance? What to do when a class design lists another class as one of its attributes — a club that has an array of members. It is the trap Cambridge sets almost every year, and after this week you have already built half of it.