Comet watches Dot's battery number on the screen and frowns. "Dot is supposed to be light," she says. "It should sip the battery, not gulp it like Pip."
Her first idea is to open rover.py and change step_use() to return 2.
Wren holds up his clipboard. "What does the evidence say? Pip and Dot both ended at battery 90 after two steps. If you change rover.py, Pip changes too, and the Pip tests expect 95."
Nova projects ScoutRover beside Rover, with one empty line inside ScoutRover glowing softly. "How can I help?" she asks. "A subclass can write its own version of a method."
Comet looks at you. "Lead programmer, where should the new number live?"
A subclass can override a method of its base class by writing a method with the same name. For a ScoutRover, Python finds the ScoutRover version first, so that is the one that runs.
Here is the surprising part. move() is written once, in Rover. It calls self.step_use(). A base class method that calls another method may end up calling the subclass version.
def move(self, grid):
"""Step forward one cell if it is open. Gives back True or False."""
d_row, d_col = STEPS[self._heading]
new_row = self._row + d_row
new_col = self._col + d_col
if not grid.is_open(new_row, new_col):
self._log.append(f"blocked at ({new_row}, {new_col})")
return False
if self._battery < self.step_use():
self._log.append("battery low")
return False
self._row = new_row
self._col = new_col
self._battery -= self.step_use()
self._log.append(f"moved to ({new_row}, {new_col})")
return True That is the move() method from your rover.py. self is the rover that is moving. When self is a ScoutRover, self.step_use() finds ScoutRover's step_use() first.
# scout_step.py
from field_data import FIELD
from grid import FieldGrid
from rover import Rover
class ScoutRover(Rover):
"""A light rover that uses less battery per step."""
def step_use(self):
return 2
grid = FieldGrid(FIELD)
pip = Rover("Pip")
dot = ScoutRover("Dot")
pip.run("FF", grid)
dot.run("FF", grid)
print(pip.status())
print(dot.status())
print(pip.step_use(), dot.step_use()) Pip at (0, 2) facing E, battery 90 Dot at (0, 2) facing E, battery 96 5 2
| Line in move() | Value for Dot's first step |
|---|---|
| d_row, d_col = STEPS[self._heading] | heading E gives (0, 1) |
| new_row, new_col | (0, 1) |
| grid.is_open(0, 1) | True, so no "blocked" |
| self._battery < self.step_use() | 100 < 2 is False |
| self._battery -= self.step_use() | battery becomes 98 |
| self._log.append(...) | "moved to (0, 1)" |
Step 4 has no error message, which makes it sneaky. Here is the run.
# scout_typo.py
from field_data import FIELD
from grid import FieldGrid
from rover import Rover
class ScoutRover(Rover):
"""A light rover that uses less battery per step."""
def step_used(self):
return 2
grid = FieldGrid(FIELD)
dot = ScoutRover("Dot")
dot.run("FF", grid)
print(dot.status()) Dot at (0, 2) facing E, battery 90
Wren builds a test rover that uses 30 per step. A short route can then check the battery low rule from week 6.
# heavy_try.py
from field_data import FIELD
from grid import FieldGrid
from rover import Rover
class HeavyRover(Rover):
"""A test rover that uses a lot of battery per step."""
def step_use(self):
return 30
grid = FieldGrid(FIELD)
bale = HeavyRover("Bale", 3, 0)
bale.run("FFFF", grid)
print(bale.get_log())
print(bale.status()) ['moved to (3, 1)', 'moved to (3, 2)', 'moved to (3, 3)', 'battery low'] Bale at (3, 3) facing E, battery 10
| Statement | True or false? |
|---|---|
| Overriding step_use() in ScoutRover changes Pip's battery use too. | ? |
| move() is written once, in Rover, and still uses each rover's own step_use(). | ? |
| A misspelled method name in a subclass always gives a traceback. | ? |
| An overriding method has the same name as the base class method. | ? |
One method in the subclass changed how a method in the base class behaves. Tomorrow you build fleet.py for real.