Chapter 5 · practice
Protecting Valid Object State
Define an Object's Valid State
The Question class from Chapter 4 does its job. It keeps a prompt beside its answer, and it decides for itself whether a response is correct:
Now look at what else that class accepts:
A question with no prompt and no answer. Python creates it without complaint, and is_correct("") cheerfully reports True. Every learner who submits an empty response gets it right.
The class collected the data together. It did not make any promise about that data being sensible.
Rules that should always be true
Think about what has to hold for a Question
the prompt is not empty, because a learner has to read something;
the answer is not empty, because otherwise an empty response counts as correct;
the points awarded are positive, because this quiz awards points for each correct answer. Other quiz designs could deliberately include unscored questions.
Rules like these have a name. A rule that should be true for an object from the moment it is created until the moment it is discarded is called an invariant: the part that does not vary.
The word matters less than the habit. When you design a class, ask what must always be true of it. Chapter 4 asked what an object should hold and what it should do. This chapter adds the third question: what should it never be allowed to become?
Which statement describes an invariant of a Question object?
Nothing is watching
Here is the uncomfortable part. Even a Question built correctly can be damaged afterwards:
This version of Question defines no checks for later attribute assignments. The object was valid a moment ago and is broken now, and nothing anywhere reported a problem.
This is not a flaw in Python. Ordinary Python attributes accept a new
Where the trouble actually appears
The damage above is quiet. That is the whole problem. A broken Question does not fail where it was broken. It fails somewhere else, later, in code that had every reason to trust it:
for question in question_list:
print(question.prompt.upper())
If one prompt is None, this AttributeError, and the None was assigned, possibly in a different
The rest of this chapter closes that gap:
Lesson 2 refuses to build an invalid object in the first place.
Lesson 3 replaces loose attribute
with that mean something. Lessons 4 and 5 separate what a class promises from how it happens to store things.
Lesson 6 permits one independently valid change through a checked setter.
Lesson 7 makes sure a change across related attributes either fully happens or does not happen at all.
Name the rules before enforcing them
Before writing any new syntax, it helps to state the rules plainly. The exercise editor holds a Question class with a points attribute added, and a function problems(question) that is supposed to return a
That function uses nothing new. It is ordinary
Task
Finish problems so it returns a
Check all three rules: the prompt must not be empty, the answer must not be empty, and points must be greater than zero. A question that breaks two rules should produce two descriptions, and a good question should produce an empty list.
The exact wording of each description is yours to choose. Run the program and read what it reports about the three questions at the bottom.