← Back to course
Python 11-12 / Week 03 / Friday
5/6
Week 03 · Guarding the State

Friday

Trace Friday: Change the inside, keep the outside
// Design a class first, then hide its data behind methods
⏱ about 30 min

Friday: Trace Friday: Change the Inside, Keep the Outside

Comet has a new idea for turn_right(). "A dict that says what comes after each heading," she says. "No index, no %. Just look it up!"

Wren holds up drive_test.py. "What does the evidence say?" he asks. "If you change turn_right() inside, will this driver still work without one change?"

Nova projects rover.py with only the inside of one method glowing. "How can I help?" she asks. "Change the glowing part. Then run the same driver and compare."

Comet cracks her knuckles. "Lead programmer, let's find out."

Procedural abstraction

Procedural abstraction gives a process a name, so you can use a method knowing what it does, not how it does it.

Its insides can change without telling the callers, as long as the method signature and what it does stay the same.

Comet's version keeps the name and the job of turn_right(), and changes only how it works:

RIGHT_OF = {"N": "E", "E": "S", "S": "W", "W": "N"}

    def turn_right(self):
        self._heading = RIGHT_OF[self._heading]

In IDLE, open rover.py and choose File, then Save As, to save a copy called rover_dict.py. Make Comet's change in that copy only.

# drive_dict.py
from rover_dict import Rover

pip = Rover("Pip")
print(pip.status())
pip.turn_right()
print(pip.get_heading())
pip.turn_right()
pip.turn_right()
pip.turn_right()
print(pip.get_heading())
pip.turn_left()
print(pip.status())
print(Rover.rovers_built)
COMPARE THE DRIVERS
  • Read the question.
  • Tap your answer.
Does drive_dict.py show the same five lines as drive_test.py?
Which line of the driver had to change?
Pip at (0, 0) facing E, battery 100
S
E
Pip at (0, 0) facing N, battery 100
1

Local variables and return values

Inside turn_left(), the name i is made each time the method runs. Does it exist outside the method? What does a mutator give back?

# local_demo.py
from rover import Rover

pip = Rover("Pip")
print(pip.turn_left())
print(pip.get_heading())
print(i)
None
N
Traceback (most recent call last):
  File "local_demo.py", line 7, in <module>
    print(i)
          ^
NameError: name 'i' is not defined. Did you mean: 'id'?
READ LOCAL_DEMO.PY
  • Read the question.
  • Tap your answer.
Why does the first line show None?
Why does print(i) stop with a NameError?

Trace drill

# turn_drill.py
from rover import Rover

pip = Rover("Pip")
for c in "RRLRR":
    if c == "R":
        pip.turn_right()
    else:
        pip.turn_left()
print(pip.get_heading())
CommandHeading after it
(start)E
RS
RW
LS
RW
RN
TRACE TURN_DRILL.PY
  • Read the question.
  • Tap your answer.
What does turn_drill.py show?
How many times does turn_right() run?

A test that does not care how

# test_rover.py
from rover import Rover


def test_start():
    r = Rover("Test")
    assert r.get_heading() == "E"
    assert r.get_battery() == 100


def test_turns():
    r = Rover("Test")
    for n in range(4):
        r.turn_right()
    assert r.get_heading() == "E"
    r.turn_left()
    assert r.get_heading() == "N"


test_start()
test_turns()
print("All Rover tests passed")
The tests only call methods. Which name of the hidden heading attribute do they never touch? Type it with its underscore.
WHY THIS EXERCISEThe tests check what the methods do, not how, so they keep passing when the inside changes.
At your computer
1. In IDLE, open rover.py and choose File, then Save As. Save the copy in fieldsim as rover_dict.py.
2. In rover_dict.py, add the RIGHT_OF line under HEADINGS and change turn_right() as shown. Save it.
3. Save drive_test.py as drive_dict.py and change its import line to from rover_dict import Rover. Run it and compare.
4. Type local_demo.py and turn_drill.py, save them in fieldsim and check your traces.
5. Type test_rover.py and run it. Your rover.py stays as it was, with % 4.
  1. Design a class first: its attributes and behaviors, in words or a diagram.
  2. Encapsulation hides a class's details. In Python, an underscore marks a name outside code should leave alone.
  3. Accessors give back a value. Mutators change instance variables and often return nothing.
  4. A class variable is shared by every object. Make lists in __init__ so each object gets its own.
  5. A method's insides can change as long as its name and its job stay the same.

Same outside, new inside, and every caller still works. Tomorrow is Mission Quest: design a class at home.

← Thursday