Special Methods (Dunder Methods) - Python

  1. The class defines __repr__ but not __str__. What is printed?
    Answer: <Code 5> <Code 5> When a class defines __repr__ but not __str__, Python's fallback behavior is the key concept being tested here. These two special methods control how objects are converted to strings, but they have a specific relationship: __str__ is what str() and print() call first, but if __str__ isn't defined, Python automatically falls back to using __repr__ instead. This means a well-defined __repr__ does double duty. In this code, Code only defines __repr__, which returns "<Code 5>". When str(code) is called, Python looks for __str__, doesn't find one, and falls back to __repr__ — producing "<Code 5>". When repr(code) is called directly, it uses __repr__ as expected — also producing "<Code 5>". So both calls return the same string, and the output is <Code 5> <Code 5>, making A correct. B is wrong because it assumes str() returns some default format like Code(5) — that's not a real Python default; Python doesn't auto-generate constructor-style representations. C reverses the logic, incorrectly assuming str() returns the repr-style string and repr() returns a constructor-style one. D is wrong because no error occurs — Python handles the missing __str__ gracefully through the fallback mechanism. A useful tip: think of __repr__ as the "universal fallback." If you only define one method, define __repr__, because it covers both repr() calls and str() calls when __str__ is absent. __str__ only covers string conversion and doesn't substitute for __repr__.
  2. What happens when the program runs?
    Answer: It prints size=3, then raises a TypeError because __len__ must return an integer. When you define special (dunder) methods in Python, you're entering into a contract with the interpreter — each method has strict rules about what it must return. This question tests whether you understand those rules for __str__ and __len__ specifically. __str__ is flexible: it just needs to return a string, which "size=3" does correctly. So print(str(shelf)) succeeds and outputs size=3. However, __len__ has a hard requirement: it must return a non-negative integer. When Python's built-in len() calls your __len__ method and receives 3.0 (a float), it raises a TypeError immediately — even though 3.0 and 3 look equivalent to us. Python enforces this strictly because len is used in contexts like memory allocation and indexing where a true integer is required. This makes B the correct answer. A is wrong because len() does not accept any numeric return value — it explicitly validates that the return type is an integer, not just something number-like. C is wrong because the TypeError doesn't occur until len(shelf) is actually called, which happens after str(shelf) successfully prints. So size=3 is printed before the error. D is wrong because Python never silently truncates floats to ints in this context — it raises an error instead of making a quiet assumption about your intent. A good rule of thumb: whenever you implement a dunder method, look up its return type contract. Python is strict about __len__ → int, __bool__ → bool, and __hash__ → int. Don't assume numeric duck-typing saves you.
  3. Which line is displayed?
    Answer: ready|Label('ready') When you use an f-string in Python, how a variable gets converted to text depends on the conversion flag you apply. Without any flag, Python calls __str__() on the object — this is meant to produce a readable, user-friendly string. When you add !r, Python calls __repr__() instead — this is meant to produce a developer-friendly, unambiguous representation (often something you could paste back into code). In the passage, label has __str__ returning "ready" and __repr__ returning "Label('ready')". The f-string f"{label}|{label!r}" first formats label with no flag (calls __str__ → "ready"), then formats label!r (calls __repr__ → "Label('ready')""). The two pieces join with |, producing ready|Label('ready') — making C correct. A (ready|ready) is wrong because it assumes both sides call __str__. The !r flag on the second {label!r} specifically overrides this and forces __repr__. B (Label('ready')|ready) reverses the logic — it mistakenly applies __repr__ to the first placeholder and __str__ to the second. The !r flag only applies to the expression it's attached to. D (Label('ready')|Label('ready')) assumes both placeholders call __repr__, ignoring that the first {label} has no flag and therefore uses __str__. A good memory trick: **!r means repr, !s means str, no flag means str by default**. Whenever you see conversion flags in an f-string, identify which dunder method each one triggers before reading the output options.
  4. Assuming the class does not define __bool__, which output is produced?
    Answer: False True 2 When Python evaluates bool() on an object, it follows a specific lookup order: first it checks for __bool__, and if that's not defined, it falls back to __len__. If __len__ returns 0, the object is considered falsy; any nonzero value makes it truthy. This question tests whether you understand that fallback chain. Here's the logic: empty = Meter(0) stores count = 0, so len(empty) would return 0. Since there's no __bool__, Python calls __len__ to determine truthiness — 0 means falsy, so bool(empty) is False. For filled = Meter(2), __len__ returns 2, which is nonzero, so bool(filled) is True. Finally, len(filled) directly calls __len__, returning 2. That gives you False True 2, confirming A is correct. B is wrong because it claims bool(empty) is True — this would only happen if __len__ returned a nonzero value, but Meter(0) clearly returns 0. C incorrectly makes bool(filled) False, ignoring that __len__ returns 2, which is truthy. D says len(filled) is 1, but __len__ returns self.count, which is 2 for filled — there's no arithmetic that produces 1 here. A handy rule to remember: Python's truthiness fallback chain is __bool__ → __len__ → always True. If you define only __len__, returning 0 makes an object falsy and anything else makes it truthy. Watch for this pattern whenever a class defines __len__ but not __bool__.
  5. Which output is produced by this code?
    Answer: [P:2] point 2 When Python needs to display an object, it chooses between two special methods depending on the context: __str__ for human-readable output (used by print() directly) and __repr__ for developer-friendly representation (used inside containers like lists). Understanding this distinction is the key to unlocking this question. When you call print([p], p), Python processes two arguments separately. The second argument, p, is printed directly, so Python calls __str__, returning "point 2". The first argument, [p], is a list — and when Python displays a list, it calls __repr__ on each element inside it, not __str__. So p inside the list renders as "P:2", giving you [P:2]. Combined, the output is [P:2] point 2, which is B. A ([point 2] point 2) assumes __str__ is used everywhere, including inside the list — that's the most common misconception here. Lists always use __repr__ for their elements, not __str__. C ([point 2] P:2) flips the logic incorrectly, applying __str__ to the list and __repr__ to the standalone print — the exact opposite of how Python works. D ([P:2] P:2) assumes __repr__ is used everywhere, ignoring that print() on a standalone object specifically invokes __str__. A handy rule to remember: **__str__ = for humans, used by print() directly; __repr__ = for containers and debugging contexts**. Any time you see an object inside a list, dict, or other container being displayed, expect __repr__ to take over.
  6. What does the program print?
    Answer: 2 1 Whenever you see a custom class with a __len__ method, remember that Python calls that method whenever you use the built-in len() function on an object of that class — it doesn't simply count the list's total elements. Here, __len__ is defined as sum(not value for value in self.done), which counts how many values in self.done are falsy (i.e., False). At initialization, self.done = [False, True, False], so there are two False values, making len(tasks) return 2. That's the first printed value. Then tasks.done[0] = True changes the list to [True, True, False], leaving only one False value. The second len(tasks) call now returns 1. The end=" " argument on the first print keeps both values on the same line with a space, giving the final output 2 1 — confirming C is correct. A (3 3) is wrong because 3 would mean counting all elements, not just False ones — a classic misread of the __len__ logic. B (2 2) is wrong because it correctly identifies the first value but ignores that tasks.done[0] is mutated before the second len() call, reducing the False count. D (3 2) combines both mistakes: it assumes total length for the first call and correctly notices the mutation effect for the second. A good strategy: whenever __len__ is overridden, read its body carefully rather than assuming it returns the raw size of any underlying collection.
  7. What is printed by the final statement?
    Answer: view=Child() Child() When Python resolves method calls in a class hierarchy, it always uses the actual runtime type of the object, not the class where a method is defined. This is called dynamic dispatch, and it's the key concept this question tests. Here, obj is a Child instance. When you call str(obj), Python invokes __str__, which Child doesn't override — so it inherits Base.__str__. Inside that method, repr(self) is called. Even though you're currently executing code defined in Base, self is still a Child object, so Python looks up __repr__ on Child — finding Child.__repr__, which returns "Child()". Therefore str(obj) produces "view=Child()". Calling repr(obj) directly also hits Child.__repr__, returning "Child()". The final output is view=Child() Child(), making D correct. A (view=Base() Base()) assumes both calls use Base's methods, ignoring that Child overrides __repr__. B (view=Base() Child()) correctly resolves repr(obj) but incorrectly assumes that calling repr(self) inside Base.__str__ somehow uses Base.__repr__ instead — it doesn't, because self is still a Child. C (view=Child() Base()) gets str(obj) right but then wrongly reverses which __repr__ is used for the direct repr(obj) call. A good rule of thumb: in Python, method resolution always follows the object's actual type at runtime, regardless of which class's code is currently executing. When you see inherited methods calling self.something, ask yourself *"what type is self really?"* — that determines which override runs.
  8. Which description correctly states what occurs?
    Answer: It prints Record(12) and then raises a TypeError. When working with Python's special (dunder) methods, it's crucial to understand not just what they do, but what type they must return. This question tests your knowledge of __str__ and __repr__ contracts. Python requires that both __str__ and __repr__ return a string. Here, __repr__ correctly returns "Record(12)" (a string), but __str__ illegally returns 12 (an integer). When you call repr(record), Python invokes __repr__, which works fine and prints Record(12). Then print(record) internally calls __str__, which returns the integer 12 — and Python immediately raises a TypeError because it expects a string, not an int. So B is correct: you get one successful print, then a crash. A is wrong because print(record) never successfully prints 12 — it raises an error before any output appears from that call. The integer 12 is returned internally but Python rejects it before displaying anything. C reverses the order of events. The repr() call happens first and succeeds without issue, so no error is raised before the first print. D swaps the output order entirely, which is wrong on two counts: repr(record) runs first (not __str__), and print(record) never successfully outputs anything due to the TypeError. A useful rule to memorize: **__str__ and __repr__ must return strings — no exceptions.** If either returns any other type, Python raises a TypeError the moment that method is invoked. Always verify return types when you see dunder method definitions in exam questions.
  9. What is printed when the code is executed?
    Answer: #7 Ticket(7) #7 When Python displays or converts objects to strings, it uses two distinct special methods: __str__ for human-readable output and __repr__ for developer-friendly representation. Knowing which method gets called in which context is exactly what this question tests. Here's how each part of print(item, repr(item), str(item)) works: print(item) implicitly calls __str__ on the object, returning #7. The built-in repr(item) explicitly calls __repr__, returning Ticket(7). Finally, str(item) explicitly calls __str__, returning #7 again. So the output is #7 Ticket(7) #7, making A correct. B is wrong because it assumes print(item) calls __repr__ — but print() always prefers __str__ when it's defined. C has the second and third values swapped, confusing which built-in function maps to which dunder method — repr() gives Ticket(7), not str(). D suggests print(item) triggers __repr__, which would only happen if __str__ were not defined (Python falls back to __repr__ in that case, but here __str__ exists). A useful tip: think of str() → __str__ → "for humans" and repr() → __repr__ → "for developers/debugging." When print() receives an object, it behaves like str() — it calls __str__. If you ever see a question where only __repr__ is defined, then both print() and repr() would produce the same output, because __str__ falls back to __repr__ automatically.
  10. Which output is produced by this code?
    Answer: 9 4 When you see Python questions involving special (dunder) methods like __len__, you need to understand the difference between how Python instances and Python's built-in functions look up those methods. Here's the key insight: when you call len(box), Python doesn't look at the instance's __dict__ — it goes directly to the class to find __len__. This is called "implicit special method lookup," and it bypasses any instance-level attributes. However, when you call box.__len__() explicitly as a regular attribute access, Python does find the instance-level version first (because that's how normal attribute lookup works). So in this code, box.__len__ = lambda: 9 adds a __len__ to the instance's dictionary, shadowing the class method only for direct attribute access. Calling box.__len__() uses the lambda and returns 9. But len(box) ignores the instance attribute entirely, reaches into Box the class, finds the original return 4, and returns 4. This confirms the correct answer is **C: 9 4**. A (9 9) is wrong because it assumes len() respects instance-level dunder overrides — it doesn't. B (4 4) is wrong because it ignores that the instance attribute does shadow the class method for direct calls. D (4 9) just has the two values reversed with no logical basis. As a study tip, remember this rule: Python's built-in functions always look up dunder methods on the class, never the instance. This is a common trap question — anytime you see instance-level dunder assignment, ask yourself whether the call goes through the built-in or directly through the attribute.

Start this quiz