Chapter 7 · practice
Representing and Comparing Objects
Make Objects Inspectable with repr()
By now you have printed a lot of attributes. You may not have printed an
Something like <__main__.Question object at 0x7f3c...>. It is not wrong. The default display identifies the class and includes an identity marker, rather than showing the attributes that would identify the question to you.
It is also useless for the thing you most often want, which is finding out what you have got.
The display exercises use smaller versions of the quiz classes so you can focus on representations. The supplied Question now exposes its point
Where this hurts
The problem shows up hardest inside a collection:
Two objects, but neither display tells you which prompt it holds. This is the output you get from a debugger, a print at two in the morning, and in every one of those situations you needed to know which question it was.
Teaching the object to describe itself
Define a __repr__ that returns a
Both lines are now readable, including the one inside a
Methods named with two underscores on each side are how Python asks an object to take part in a language feature. You have written one already: __init__ is how an object takes part in construction. __repr__ is how it takes part in being displayed. Normally you use the language operation, such as calling a class or using repr(object), and Python calls the special method for you.
What a good repr looks like
The convention is worth following exactly, because it pays off:
f"Question({self.prompt!r}, {self.answer!r}, {self.points!r})"
When practical, aim for a string that looks like the code you would write to create an equivalent object. For an object that contains history or external resources, a concise, clearly labeled summary is more honest than pretending the text is an executable constructor call.
That is what !r is doing. Inside an !r asks for the repr of a value rather than its ordinary text form, which is the difference between these:
Without !r, a string appears bare and you cannot tell 3 the string from 3 the number. With it, the quotes are visible. In a __repr__, always use !r for values that might be strings.
Why does the __repr__ above use {self.points!r} rather than {self.points}?
repr in the tools you already use
Once a class has a __repr__, it appears everywhere without further effort:
The repr() built-in calls it. Containers call it for their contents. In the browser REPL beside this course, entering a variable’s name shows its repr, which is why an object with a good one is so much easier to explore.
Note that the REPL and containers use repr, while print on the object itself will prefer a different method once a class defines one. That is the next lesson.
Keep it cheap and keep it total
Two habits worth forming now.
A __repr__ runs in exactly the situations where things are already going wrong: debuggers, error messages, logs. It should never do slow work, and it should never raise. A repr that fails while you are diagnosing a failure hides the problem you were chasing.
It should also be honest about what it shows. Two distinct value-like objects may reasonably have the same repr. If identity matters for a particular diagnostic, add a stable identifier only when the model actually has one; do not invent a constructor
Task
Give both classes a useful, honest __repr__.
Question should produce Question('What does len(...) return?', '3', 2), with each
QuizAttempt should produce QuizAttempt(learner='Mina', response_count=2). That is explicitly a summary, not a call its one-
Neither __repr__ may raise, even for an attempt with no responses. Run the program and inspect both