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

Thursday

Shared by all
// Design a class first, then hide its data behind methods
⏱ about 30 min

Thursday: Shared by All

Comet points at two lines in rover.py: rovers_built = 0, and Rover.rovers_built += 1. "We typed these yesterday," she says. "But Pip only has one battery. Whose count is this?"

Wren makes three rovers on his clipboard: Pip, Dot and Moss. "What does the evidence say?" he asks. "Let's print the count after each one."

Nova projects three rovers, each with its own battery, and one counter floating above them all. "How can I help?" she asks. "Some values belong to each object. Some belong to the class."

Comet nods slowly. "Lead programmer, predict the counts."

Instance variables and class variables

An instance variable is unique to each object, like _battery. A class variable is shared by every object of the class.

rovers_built is written in the class body, not in __init__, so it belongs to the class. Each new rover adds 1 to it.

# fleet_count.py
from rover import Rover

print(Rover.rovers_built)
pip = Rover("Pip")
dot = Rover("Dot", 2, 3)
print(Rover.rovers_built)
moss = Rover("Moss")
print(Rover.rovers_built)
print(pip.rovers_built)
PREDICT FLEET_COUNT.PY
  • Read the question.
  • Tap your answer.
What does the second print show?
What does print(pip.rovers_built) show?
0
2
3
3

The shared-list trap

A class variable that holds a list is shared too. Here is a bug Wren found in a tray class.

# tray_bug.py
class Tray:
    """A tray that holds sample labels. This version has a bug."""

    labels = []

    def __init__(self, name):
        self.name = name

    def add(self, label):
        self.labels.append(label)


a = Tray("Tray A")
b = Tray("Tray B")
a.add("S01")
b.add("S02")
print(a.name, a.labels)
print(b.name, b.labels)
Tray A ['S01', 'S02']
Tray B ['S01', 'S02']

A list stored as a class variable is shared by every object, which is usually a mistake. Make the list in __init__ so each object gets its own.

# tray_bug.py, fixed
class Tray:
    """A tray that holds sample labels."""

    def __init__(self, name):
        self.name = name
        self.labels = []

    def add(self, label):
        self.labels.append(label)


a = Tray("Tray A")
b = Tray("Tray B")
a.add("S01")
b.add("S02")
print(a.name, a.labels)
print(b.name, b.labels)
READ THE TRAYS
  • Read the question.
  • Tap your answer.
In tray_bug.py, why does Tray A show S02?
What did the fix change?

One more trap: counting through self

# count_bug.py
class Counter:
    made = 0

    def __init__(self):
        self.made += 1


a = Counter()
b = Counter()
print(Counter.made, a.made, b.made)
TRACE COUNT_BUG.PY
  • Read the question.
  • Tap your answer.
What does count_bug.py show?
Which line in rover.py avoids this trap?
At your computer
1. Type fleet_count.py, save it in fieldsim and run it. Add a fourth rover and predict the new counts.
2. Type tray_bug.py and run it. Then fix it as shown and run it again.
3. Type count_bug.py and run it. Change self.made to Counter.made and predict the new output.
StatementTrue or false?
A class variable is shared by every object of the class.?
Each rover has its own copy of rovers_built.?
A list made in __init__ belongs to one object.?
self.made += 1 changes the class variable made.?
WHY THIS EXERCISEClass variables are shared, instance variables made in __init__ are each object's own, and self.made += 1 made a new instance value.

Shared values are useful, and shared lists are a trap. Tomorrow is Trace Friday: change the inside, keep the outside.

← Wednesday