Trace Friday starts with pencils, not keyboards. Wren rules four columns on graph paper: the call, the subclass, the base class, the code that runs.
Comet wants to skip ahead. "We know it works. The demo printed the right numbers."
"What does the evidence say?" Wren asks. "We saw the output. We have not shown why each number came out that way."
Nova projects a chain of three boxes, one under another, with an arrow climbing from the bottom box upward. "How can I help?" she asks. "Start at the object's own class. Climb until you find the name."
Comet sharpens her pencil and slides the paper to you. "Lead programmer, you trace. I will check."
For a method call, Python searches the object's own class first. If the name is not there, it goes down the chain of base classes, one class at a time.
A base class method that calls another method, like move() calling self.step_use(), starts the search again from the object's own class.
| Call on moss (a HaulerRover) | In HaulerRover? | In Rover? | Code that runs |
|---|---|---|---|
| moss.run("FF", grid) | no | yes | Rover.run |
| self.move(grid), inside run | no | yes | Rover.move |
| self.step_use(), inside move | yes | yes | HaulerRover.step_use |
| moss.status() | yes | yes | HaulerRover.status |
| super().status(), inside status | skipped | yes | Rover.status |
Wren adds a third level: a tiny test scout built on ScoutRover. Trace it on paper before you look at the output.
# mini_scout.py
from field_data import FIELD
from fleet import ScoutRover
from grid import FieldGrid
from sample import Sample
class MiniScout(ScoutRover):
"""A tiny test scout: one battery unit per step."""
def step_use(self):
return 1
grid = FieldGrid(FIELD)
bit = MiniScout("Bit")
bit.run("FF", grid)
print(bit.status())
print(bit.pick_up(Sample("T1", "clay", 10)))
print(bit.pick_up(Sample("T2", "clay", 10))) | Call on bit | MiniScout | ScoutRover | Rover |
|---|---|---|---|
| __init__ | no | yes (calls super) | yes |
| step_use | yes | yes | yes |
| status | no | yes (calls super) | yes |
| pick_up | no | no | yes |
Scout Bit at (0, 2) facing E, battery 98 True False
A ScoutRover is a Rover: isinstance(dot, Rover) is True. That is an is-a link, made by a subclass.
A Rover has samples. An object has a has-a relationship with its instance variables, like _samples, a list of Sample objects. That link is made by storing objects, not by a subclass.
| Statement | Is-a or has-a: true or false? |
|---|---|
| HaulerRover is a Rover. | ? |
| A Sample is a Rover. | ? |
| A Rover has a list of Samples. | ? |
| Sample should be a subclass of Rover, because rovers carry samples. | ? |
Wren writes tests for the fleet in the same style as test_fieldsim.py. Read them and predict the output.
# test_fleet.py
# Tests for fleet.py. Run this file: it prints one line when all pass.
from field_data import FIELD
from fleet import HaulerRover, ScoutRover
from grid import FieldGrid
from rover import Rover
def test_scout():
grid = FieldGrid(FIELD)
dot = ScoutRover("Dot")
assert dot.move(grid)
assert dot.get_battery() == 98
assert dot.status().startswith("Scout ")
assert isinstance(dot, Rover)
def test_hauler():
grid = FieldGrid(FIELD)
moss = HaulerRover("Moss", 3, 0)
moss.run("FFF", grid)
assert moss.get_col() == 3
assert moss.get_battery() == 76
test_scout()
test_hauler()
print("All fleet tests passed") All fleet tests passed
You traced three levels of classes on paper and predicted every line. Tomorrow is the Mission Quest.