0%

Representing and Comparing Objects · practice

Define Value Equality

Question is a , so two questions with the same contents should be equal. Telling Python that takes one .

Defining ==

Try it

== now compares contents. is still compares objects, and always will: identity is not something a class gets to redefine.

Everything built on == improves at the same time:

Try it

The membership test from the previous lesson now works, and so do .index(), .remove(), and comparing two .

Comparing with something that is not a question

That __eq__ has a bug, and it is the one everybody writes first:

first == "What does len('cat') return?"

AttributeError, because a has no .prompt. Comparing two things should never raise. == is used by in, list comparisons, .index(), .remove(), and test frameworks, often on collections whose contents you did not choose. Sorting is different: it uses ordering comparisons or a key .

The fix is to check the type first:

Try it

isinstance(other, Question) asks whether other is a Question. If it is not, the method returns NotImplemented.

That looks like it should be a mistake, and it is worth one paragraph. NotImplemented is a special value meaning “I do not know how to compare myself with that.” Python takes it as an answer, not as a result: it then asks the other object whether it knows, and if neither does, it falls back to comparing identity, which gives False. Returning False directly would also work here, and it would quietly stop some other class from ever declaring itself equal to a question. Returning NotImplemented is the habit worth having.

Why does __eq__ check isinstance(other, Question) before comparing attributes?

Choosing what counts

__eq__ is a design decision, not a formality. You are declaring which parts of an object make it what it is.

Consider a Response that also records when it was answered:

class Response:
    def __init__(self, question, given, answered_at):
        ...

Should two responses to the same question, with the same text, one second apart, be equal? There is no universal answer. If Response is a value describing what was said, the time is incidental and should be left out. If it is a record of an event, the time is part of what happened, and two events are never the same event.

Two rules that hold regardless:

Prefer stable value state. objects such as lists can legitimately compare equal now and unequal later. For a class meant to behave as a value, though, read-only public state makes equality easier to reason about. The next lesson explains the stricter rule required before an object may be hashed and used as a .

Make the repr useful for understanding equality. Showing the compared fields helps explain why two values differ. A summary can omit detail, so matching reprs alone do not prove that objects are equal.

The habit

Equality is expected to behave like equality: an object equals itself, a == b agrees with b == a, and if a == b and b == c then a == c. Comparing the ordinary strings and numbers in this example preserves those expectations. Normalizing a string on both sides before comparing can preserve them too. A rule such as “amounts within one point are equal” does not: 1 matches 2 and 2 matches 3, but 1 does not match 3. Keep that kind of closeness check in a separate method.

The exercise adds equality to two read-only values: Question and Points, a positive amount. It leaves equality off the identity-based QuizAttempt.

Task

Give equality to the two classes that deserve it, and to no others.

Question is equal when its prompt, answer, and points all match. Points is equal when its amount matches.

Both must return NotImplemented when compared with something that is not their own class, so that question == "text" reports False instead of raising.

Leave QuizAttempt alone. Two attempts are the same attempt only when they are the same , which is what Python already does, so adding an __eq__ there would be actively wrong.

Run the program. The find_duplicate at the bottom should report the repeated question, which it cannot currently do at all.