Comet spreads her sketch notebook across the workbench. Two new rovers fill the page: a small, quick scout and a wide hauler with a big sample bin.
"What can we make?" she asks. "I will copy rover.py twice, rename the class, and change a few numbers. Done by lunch."
Wren taps his clipboard. "What does the evidence say? rover.py passes every test we wrote. Two copies means three places to fix the next bug."
Nova hovers above the notebook and projects a small tree: one box at the top, two branches below. "How can I help?" she asks. "A new class can start from a class you already trust."
Comet chews her pencil, then turns to you. "Lead programmer, can a class start from another class?"
A derived class, or subclass, names its base class in brackets: class ScoutRover(Rover):. Rover is the base class.
When you call a method on a ScoutRover, Python looks in ScoutRover first. If the method is not there, it looks in the base class, Rover.
This first ScoutRover has only a docstring as its body. It adds nothing of its own yet. Read it and predict before you run it.
# scout_try.py
from field_data import FIELD
from grid import FieldGrid
from rover import Rover
class ScoutRover(Rover):
"""A light rover. For now it adds nothing of its own."""
grid = FieldGrid(FIELD)
dot = ScoutRover("Dot")
print(dot.status())
dot.run("FF", grid)
print(dot.status())
print(dot.get_log())
print(Rover.rovers_built) Dot at (0, 0) facing E, battery 100 Dot at (0, 2) facing E, battery 90 ['moved to (0, 1)', 'moved to (0, 2)'] 1
Dot moved, logged its moves and counted itself in Rover.rovers_built. Every one of those lines of code lives in rover.py. ScoutRover inherited them all.
| Call in scout_try.py | Look in ScoutRover | Look in Rover | Code that runs |
|---|---|---|---|
| ScoutRover("Dot") | no __init__ | found | Rover.__init__ |
| dot.status() | no status | found | Rover.status |
| dot.run("FF", grid) | no run | found | Rover.run |
| dot.get_log() | no get_log | found | Rover.get_log |
This is the error you will see in step 4. Without a base class, ScoutRover has no status() to find.
# scout_oops.py
class ScoutRover:
"""A light rover. For now it adds nothing of its own."""
dot = ScoutRover()
print(dot.status()) Traceback (most recent call last):
File "scout_oops.py", line 7, in <module>
print(dot.status())
^^^^^^^^^^
AttributeError: 'ScoutRover' object has no attribute 'status' # scout_turns.py
from field_data import FIELD
from grid import FieldGrid
from rover import Rover
class ScoutRover(Rover):
"""A light rover. For now it adds nothing of its own."""
grid = FieldGrid(FIELD)
dot = ScoutRover("Dot", 2, 0)
dot.turn_right()
dot.run("FX", grid)
print(dot.get_heading())
print(dot.get_log())
print(dot.status()) S ['moved to (3, 0)', 'unknown command X'] Dot at (3, 0) facing S, battery 95
| Statement | True or false? |
|---|---|
| A subclass must repeat every method of its base class. | ? |
| class ScoutRover(Rover): makes Rover the base class. | ? |
| Python looks for a method in the subclass first, then in the base class. | ? |
| Making a ScoutRover still runs Rover.__init__ when ScoutRover has none. | ? |
One line, class ScoutRover(Rover):, gave Dot every skill Pip has. Tomorrow you make Dot behave differently.