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

Tuesday

Hide the state
// Design a class first, then hide its data behind methods
⏱ about 30 min

Tuesday: Hide the State

Wren pins yesterday's output to the board: battery -40, facing up. "What does the evidence say?" he asks. "Any line of code anywhere could do that to Pip."

Comet frowns. "So we lock the battery away? Then how does anyone read it?"

Nova projects Pip inside a box with a few small windows. "How can I help?" she asks. "Keep the values inside. Open a window, a method, for each thing outside code may ask."

Comet sketches the windows in her notebook. "get_row, get_col, get_heading, get_battery. Lead programmer, let's build them."

Encapsulation

Encapsulation keeps a class's details hidden from other classes. Access to attributes should stay inside the class.

Python has no truly private variables. By convention, a name that starts with an underscore, like _battery, is not part of the public interface. Other code should leave it alone.

# rover_hidden.py
class Rover:
    """A survey rover on the Larkspur test field."""

    def __init__(self, name, row=0, col=0):
        self.name = name
        self._row = row
        self._col = col
        self._heading = "E"
        self._battery = 100

    def get_row(self):
        return self._row

    def get_battery(self):
        return self._battery

    def status(self):
        return (f"{self.name} at ({self._row}, {self._col}) facing "
                f"{self._heading}, battery {self._battery}")


pip = Rover("Pip")
dot = Rover("Dot", 2, 3)
print(pip.status())
print(dot.status())
print(dot.get_row(), pip.get_battery())
PREDICT ROVER_HIDDEN.PY
  • Read the question.
  • Tap your answer.
What does print(dot.status()) show?
What does the last line show?
Pip at (0, 0) facing E, battery 100
Dot at (2, 3) facing E, battery 100
2 100

Accessors give back a value

An accessor method lets other code read a value without reaching into the object. Here b = 0 only points the name b at a new number. It does not reach back into Pip. A list is different: week 4 shows why get_samples() must hand back a copy on purpose.

# accessor_copy.py
from rover_hidden_class import Rover

pip = Rover("Pip")
b = pip.get_battery()
b = 0
print(b, pip.get_battery())

The underscore is a sign, not a lock

# underscore.py
from rover_hidden_class import Rover

pip = Rover("Pip")
pip._battery = -40
print(pip.status())
READ THE TWO RUNS
  • Read the question.
  • Tap your answer.
In accessor_copy.py, did b = 0 change Pip's battery?
In underscore.py, why did the battery become -40?
Which line should outside code use to read the battery?

The crew's rule from now on: outside code never touches a name that starts with an underscore. It calls methods instead.

At your computer
1. Copy the class part of rover_hidden.py, without its comment line and the demo lines at the bottom, into a new file. Save it in fieldsim as rover_hidden_class.py.
2. Type accessor_copy.py and underscore.py, save them in fieldsim and run each one.
3. In rover_hidden_class.py, add get_col() and get_heading() in the same style as get_row().
4. In accessor_copy.py, add print(pip.get_heading()). Predict, then run it.
5. Misspell get_battery as get_batery in accessor_copy.py. Run it, read the AttributeError, then fix it.
StatementTrue or false?
Encapsulation keeps a class's details hidden from other classes.?
A name starting with an underscore cannot be changed from outside.?
An accessor gives back a value without letting other code set the instance variable directly.?
Outside code should read Pip's battery through get_battery().?
WHY THIS EXERCISEThe underscore is a convention, not a lock, so the crew keeps outside code to the methods.

Pip's state now sits behind methods. Tomorrow is Sim Lab: the real rover.py, with turns.

← Monday