0%

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 :

Try it

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 as read-only, along with its prompt and answer. The later equality and hashing lessons will use that stable public state.

Where this hurts

The problem shows up hardest inside a collection:

Try it

Two objects, but neither display tells you which prompt it holds. This is the output you get from a debugger, a panel, a failed assertion, and a hurried 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 named __repr__ that returns a :

Try it

Both lines are now readable, including the one inside a , which is the one that matters.

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:

Try it

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:

Try it

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 the class cannot accept.

Task

Give both classes a useful, honest __repr__.

Question should produce Question('What does len(...) return?', '3', 2), with each shown as it would be written in code.

QuizAttempt should produce QuizAttempt(learner='Mina', response_count=2). That is explicitly a summary, not a call its one- constructor could execute. It cannot show every response without becoming unreadable, so it reports the count instead.

Neither __repr__ may raise, even for an attempt with no responses. Run the program and inspect both : one contains questions, and the last contains an attempt with no responses.