← Back to course
Python 11-12 / Week 05 / Thursday
4/6
Week 05 · Grids

Thursday

Stay in bounds
// The test field as rows and columns
⏱ about 30 min

Thursday: Stay in Bounds

Comet is testing cells near the edge. "Row 5 stops the program," she says. "But row -1 does not. It hands me a cell. So -1 is fine?"

Wren frowns at his clipboard. "What does the evidence say?" he asks. "Which cell did it hand you? The field has no row -1."

Nova projects the field and lights up the bottom row. "How can I help?" she asks. "Compare cells[-1] with cells[4]. Then ask the grid before you look."

"Lead programmer," Comet says, "one of these is loud and one is quiet. Which is worse?"

Off the edge

IndexError means a list index is out of range. The field has rows 0 to 4, so row 5 is off the edge.

The field here is made-up data from the FieldSim story. Watch what row -1 does.

# out_of_bounds.py
from field_data import FIELD

cells = []
for row in FIELD:
    cells.append(list(row))
print(cells[4][5])
print(cells[-1][5])
print(cells[-1] == cells[4])
print(cells[5][0])
PREDICT OUT_OF_BOUNDS.PY
  • Read the question.
  • Tap your answer.
What does print(cells[-1][5]) show?
Which line stops the program?
*
*
True
Traceback (most recent call last):
  File "out_of_bounds.py", line 10, in <module>
    print(cells[5][0])
          ~~~~~^^^
IndexError: list index out of range

Row 5 is loud: the program stops. Row -1 is quiet: it reads the last row and carries on.

A quiet wrong answer is a logic error. The program behaves wrongly, and only testing finds it.

Ask first: in_bounds and is_open

FieldGrid answers the question before anyone reads a cell:

    def in_bounds(self, row, col):
        return 0 <= row < self.height() and 0 <= col < self.width()

    def cell(self, row, col):
        return self._cells[row][col]

    def set_cell(self, row, col, mark):
        self._cells[row][col] = mark

    def is_open(self, row, col):
        return self.in_bounds(row, col) and self.cell(row, col) != "#"
# bounds_check.py
from field_data import FIELD
from grid import FieldGrid

grid = FieldGrid(FIELD)
for spot in [(0, 0), (1, 1), (-1, 0), (5, 0), (4, 5), (0, 6)]:
    r = spot[0]
    c = spot[1]
    print(spot, grid.in_bounds(r, c), grid.is_open(r, c))
PREDICT BOUNDS_CHECK.PY
  • Read the question.
  • Tap your answer.
What does the (1, 1) line show?
What does the (-1, 0) line show?
What does the (5, 0) line show?
(0, 0) True True
(1, 1) True False
(-1, 0) False False
(5, 0) False False
(4, 5) True True
(0, 6) False False

Why is_open never falls off

is_open(5, 0) never reached self.cell(5, 0). This shell session shows why: when the left side of and is False, the right side is not run.

>>> def shout():
...     print("shout ran")
...     return True
...
>>> False and shout()
False
>>> True and shout()
shout ran
True
StatementTrue or false?
cells[-1] stops the program with an IndexError.?
is_open(5, 0) returns False without reading a cell.?
in_bounds(0, 6) is True because 6 is a column number.?
A quiet wrong answer is harder to notice than a crash.?
WHY THIS EXERCISENegative indexes read from the end quietly, so the grid checks bounds first and only then reads a cell.

One last slip to read. In grid.py, Comet once typed row <= self.height() in in_bounds. Here is bounds_check.py with that slip:

(0, 0) True True
(1, 1) True False
(-1, 0) False False
Traceback (most recent call last):
  File "bounds_check.py", line 9, in <module>
    print(spot, grid.in_bounds(r, c), grid.is_open(r, c))
                                      ~~~~~~~~~~~~^^^^^^
  File "grid.py", line 29, in is_open
    return self.in_bounds(row, col) and self.cell(row, col) != "#"
                                        ~~~~~~~~~^^^^^^^^^^
  File "grid.py", line 23, in cell
    return self._cells[row][col]
           ~~~~~~~~~~~^^^^^
IndexError: list index out of range
At your computer
1. In your fieldsim folder, type out_of_bounds.py and run it. Read the IndexError calmly.
2. Type bounds_check.py and run it. Add (2, 3) to the list, predict its line, then run it.
3. In grid.py, change row < self.height() to row <= self.height(). Run bounds_check.py and read the IndexError.
4. Change grid.py back to <, save, and run bounds_check.py once more.

Bounds checks keep Pip on the field. Tomorrow you will trace the neighbors of a cell.

← Wednesday