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?"
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]) *
*
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.
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)) (0, 0) True True (1, 1) True False (-1, 0) False False (5, 0) False False (4, 5) True True (0, 6) False False
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 | Statement | True 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. | ? |
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 Bounds checks keep Pip on the field. Tomorrow you will trace the neighbors of a cell.