When `str(obj)` runs, does Python look up `__str__` on the instance or on its type?
answer
- Two places a method name could live
- The object model asks the class
- type(obj), not obj.__dict__
- Descriptors honoured, __getattribute__ skipped
- Explicit obj.__str__() takes the other path
basics
~10 sPython finds special methods on the object's type, never on the object itself. str(obj) calls type(obj).str(obj), so a str sitting in the instance dictionary is ignored. Special methods belong in the class body.
solid answer
~40 sImplicit invocations — `str()`, `len()`, `with`, operators, iteration — use *implicit special method lookup*: CPython walks the MRO of `type(obj)`, applies the descriptor protocol to whatever it finds, and calls it with the instance as the first argument. It never reads `obj.__dict__` and it never calls `__getattribute__` or `__getattr__`. So `str(obj)` is effectively `type(obj).__str__(obj)`; a `__str__` assigned onto one instance is dead weight, even though the explicit call `obj.__str__()` would find it, because that is an ordinary attribute lookup taking the other path. The same rule scales up: a class object is an instance of its metaclass, so `len(MyClass)` looks for `__len__` on the metaclass, not in `MyClass`'s body.
code
python · 9 linesclass Greeter:
def __str__(self):
return "from the class"
g = Greeter()
g.__str__ = lambda: "from the instance"
print(str(g)) # from the class
print(g.__str__()) # from the instancego deeper
Recall the one-line rule: special methods are found on the class, so write them in the class body. Be ready to say that attaching a dunder to a single object does nothing.
Explain the two lookup paths concretely — object.__getattribute__ consults the instance dictionary, implicit protocol lookup consults only the type's MRO — and show that str(obj) and obj.__str__() can return different things.
Show where this bites in production code: wrappers and proxies, runtime patching that must target the class, and the fact that object already supplies __str__ and __eq__ so those failures are silent rather than loud.
Frame it as an object-model guarantee rather than trivia: protocols are a property of a type, which is what lets the interpreter cache them in slots. Any design that wants per-object behaviour must encode it as class-level dispatch on instance state.
Python has two distinct attribute paths, and this question is entirely about which one a protocol takes. ## The ordinary path `obj.name` compiles to a call to `type(obj).__getattribute__(obj, "name")`. The default implementation, `object.__getattribute__`, looks for a data descriptor in the MRO of `type(obj)`, then in `obj.__dict__`, then for a non-data descriptor or plain class attribute in the MRO; if all of that fails and the class defines `__getattr__`, that hook is called as a last resort. This path sees the instance dictionary, and it is what most people picture when they hear "attribute lookup". ## The implicit path `str(obj)`, `len(obj)`, `obj[0]`, `a + b`, `for x in obj`, `with obj:` and every other builtin- or syntax-driven protocol do not take that path. The language reference calls what they do *implicit special method lookup*: the interpreter searches the MRO of `type(obj)` for the dunder directly, invokes the found object's `__get__` if it is a descriptor, and calls the result with the instance as the first argument. The instance dictionary is not consulted, and `__getattribute__`/`__getattr__` are not invoked at any level. So `str(obj)` behaves like `type(obj).__str__(obj)`, and the two paths can genuinely disagree: ```python class Greeter: def __str__(self): return "from the class" g = Greeter() g.__str__ = lambda: "from the instance" print(str(g)) # from the class print(g.__str__()) # from the instance ``` The assignment is not an error and not silently dropped — the lambda really is in `g.__dict__`, and the explicit call really does find it. It simply has no effect on any implicit invocation. ## What follows from the rule **Per-instance dunders are inert.** You cannot give one object its own `__str__`, `__len__` or `__add__` by assignment. If two objects of the same class must behave differently, the branch has to live inside a single class-level dunder that reads instance state. **Class-level assignment does work, and works retroactively.** `Greeter.__str__ = lambda self: "patched"` takes effect for objects created before the assignment, because the lookup goes to the type every single time and `type.__setattr__` refreshes the type's internal slots and invalidates the type's method cache. **`__getattr__` cannot supply protocols.** A class whose `__getattr__` forwards everything to a wrapped object still fails `len()` and `with`, because those never reach `__getattr__`. A wrapper has to declare each special method it wants to support on its own class. **Descriptors are still honoured.** The lookup finds an object in the type and calls its `__get__`, which is exactly why plain functions become bound methods here, and why a `classmethod` or `property` placed under a dunder name is still invoked as a descriptor. Only the instance dictionary and the attribute hooks are skipped, not the descriptor protocol. **Class objects get their dunders from the metaclass.** `MyClass` is an instance of `type` (or of a custom metaclass), so `len(MyClass)` looks for `__len__` on `type(MyClass)`. A `__len__` written in the class body serves *instances* of the class; it does nothing for the class object itself. **Some dunders are always found, because `object` defines them.** `object` supplies `__str__`, `__repr__`, `__eq__`, `__hash__`, `__format__` and more, so `str(x)` and `x == y` never raise "not supported" — they quietly fall back to identity-based defaults. `object` deliberately defines no `__bool__`, and with no `__len__` either, every object is truthy by default. That asymmetry is why some mistakes on this topic explode loudly and others never surface. ## Why the language works this way Speed and predictability. At class creation CPython stores the class's dunders into C-level slots on the type object (a string slot, an iteration slot, a length slot, and so on), and the operator implementations read those slots. If protocols were resolved through normal attribute lookup, every `+`, every iteration step and every `len()` would have to probe an instance dictionary first — and any object that happened to carry an attribute named `__len__` would accidentally change the semantics of the language for itself. Since 3.11 the specializing adaptive interpreter (PEP 659) leans on this further, caching resolved special methods per call site keyed on the type's version tag; the cache is invalidated when the type is modified, which is precisely why class-level patching stays correct. The rule is unchanged in 3.14. ## Checking it yourself The rule is easy to confirm in a REPL, and worth confirming once rather than memorising. Put a dunder on an instance and watch the two calls disagree; put the same dunder on the class and watch every existing instance change behaviour at once. `vars(obj)` shows the instance dictionary, `type(obj).__mro__` shows the exact search order the protocol uses, and `"__str__" in vars(type(obj))` answers "did this class define it, or is it inherited from `object`?" — a question that decides whether a missing protocol will raise loudly or fall back silently. The practical summary for everyday code: behaviour belongs to the class, data belongs to the instance. Every time you want an object to *act* differently, the change goes in the class body and reads instance state; every time you want an object to *hold* something different, it goes in the instance. Special method lookup is simply that separation enforced by the interpreter rather than by convention.
- Where must `__len__` live for `len(MyClass)` to work on the class object itself?On the metaclass. `MyClass` is an instance of `type(MyClass)`, so the same rule applies one level up: `len(MyClass)` looks for `__len__` on the metaclass. A `__len__` written in the class body gives *instances* of `MyClass` a length and does nothing for the class object.
- Does implicit special method lookup still honour descriptors such as `property`?Yes. The lookup walks the type's MRO and then calls `__get__` on whatever it finds, which is exactly how a plain function becomes a bound method. Only the instance dictionary and the `__getattribute__`/`__getattr__` hooks are bypassed; the descriptor protocol runs normally.
A passport is checked against the issuing country's records, not against what the traveller has written in their own notebook. Python asks the type, not the object.
saying these in an interview costs you the question
- Says Python checks the instance dictionary for dunders first
- Claims assigning obj.__str__ changes what print(obj) shows
- Believes str(obj) and obj.__str__() always resolve identically
- Expects a class's __getattr__ to supply missing special methods
- Thinks a __len__ in the class body gives the class object a length