BSEH Secondary Computer Science study materials
BSEH Secondary Computer Science study resources

The Marks Summary That Keeps Growing

BSEH Secondary Computer Science · Put your knowledge to work in context.

All case studies

The Marks Summary That Keeps Growing

The computer club secretary writes a Python program to summarise marks from a text file. Each line is meant to be 'name,marks', but some lines are blank and one line has 'absent'. The current code is:

def add_score(score, scores=[]): scores.append(score) return scores

def summary(lines): below = [] for line in lines: if line.strip() == '': continue name, mark = line.split(',') if mark == 'absent': continue all_scores = add_score(int(mark)) if int(mark) < 40: below.append(name) avg = sum(all_scores) // len(all_scores) print('Average:', avg) print('Below 40:', below)

summary(['Asha,72', 'Ravi,38', '', 'Meera,absent', 'John,91']) summary(['Sam,55', 'Tara,29'])

The secretary reports that the first call prints Average: 67 and Below 40: ['Ravi'], which looks correct. The second call prints Average: 57 instead of 42, and the list held by add_score now contains marks from both classes. The file also contains a line 'Kumar, A.,78' that crashes the program.

1. Trace both calls. Show the state of add_score's default list after each mark and explain why 57 appears.

Read the worked answer

First call: Asha 72 appends 72, so the default list is [72] and all_scores refers to it. Ravi 38 appends 38, giving [72, 38]; all_scores still refers to the same list. John 91 appends 91, giving [72, 38, 91]. The blank line and 'absent' are skipped. The average is (72 + 38 + 91) // 3 = 201 // 3 = 67, and below is ['Ravi'].

Second call: the default list is not reset because default arguments are evaluated once. Sam 55 appends 55, giving [72, 38, 91, 55]. Tara 29 appends 29, giving [72, 38, 91, 55, 29]. all_scores again refers to this same list. The sum is 285 and the length is 5, so 285 // 5 = 57. The correct second-class average would be (55 + 29) // 2 = 42. The below list is local to summary, so it still prints ['Tara']; the hidden shared list is the bug.

2. Rewrite add_score and summary to fix the mutable default, handle absent/blank lines, and avoid a divide-by-zero when no valid marks exist. Use a float average or explain your choice.

Read the worked answer

Use None as the default and create a new list inside the function. Keep the per-call scores in summary local, validate the mark, and guard the empty case.

def add_score(score, scores=None):
    if scores is None:
        scores = []
    scores.append(score)
    return scores

def summary(lines):
    scores = []
    below = []
    for line in lines:
        line = line.strip()
        if not line:
            continue
        parts = line.split(',')
        if len(parts) != 2:
            continue
        name, mark = parts[0].strip(), parts[1].strip()
        if mark.lower() == 'absent':
            continue
        try:
            m = int(mark)
        except ValueError:
            continue
        scores.append(m)
        if m < 40:
            below.append(name)
    if not scores:
        print('No valid marks')
        return
    avg = sum(scores) / len(scores)
    print(f'Average: {avg:.2f}')
    print('Below 40:', below)

I used / rather than // because an average of marks is usually reported with a decimal. The guard prevents division by zero. The first call gives 67.00 and the second gives 42.00, with no shared state.

3. The line 'Kumar, A.,78' crashes with 'too many values to unpack'. Propose a parsing rule that handles names containing commas without breaking the two-field format, and give two test cases (one valid, one invalid) with expected behaviour.

Read the worked answer

The marks field has no commas, so split from the right exactly once: name, mark = line.rsplit(',', 1). This keeps 'Kumar, A.' as the name and '78' as the mark. Then strip whitespace and validate the mark with int().

Valid test: 'Kumar, A.,78' -> name 'Kumar, A.', mark 78; the student is included in the average and checked against 40. Invalid test: 'Rita,abc' -> rsplit gives name 'Rita' and mark 'abc'; int() raises ValueError, which is caught, the row is skipped or logged, and the program continues. A second invalid case, 'Solo', has no comma and should be skipped with a warning rather than crashing. This rule is safer than split(',') for names, but a full CSV parser is still better if fields can contain quoted commas or escapes.