Exercise 3: Counting find_method()'s Own Recursive Calls — Possible Solution ==================================================================== THE INSTRUMENTATION ------------------------------ call_count = [0] original = WispClass.find_method def counted(self, name): call_count[0] += 1 return original(self, name) WispClass.find_method = counted # class A { whoAmI() { return "A"; } } class B < A {} class C < B {} # print C().whoAmI(); RESULT ------------------------------ find_method() called 6 times total for C().whoAmI() WHY 6, NOT 2 ------------------------------ It's tempting to count "one call to resolve whoAmI, one call to check init during construction" = 2. That undercounts because find_method is RECURSIVE -- every level of the superclass chain it has to walk through is its own separate call to the same method, not one call that internally loops. Constructing C() first checks for an 'init' method, and NONE of A, B, or C ever define one: C.find_method("init") -> not in C.methods -> calls B.find_method("init") -> not in B.methods -> calls A.find_method("init") -> not in A.methods -> A.superclass is None -> returns None = 3 calls, ending in a miss (fine -- Chapter 6's own "no initializer" path handles a class with no init correctly, and C() takes zero arguments here anyway) Then C().whoAmI() looks up 'whoAmI': C.find_method("whoAmI") -> not in C.methods -> calls B.find_method("whoAmI") -> not in B.methods -> calls A.find_method("whoAmI") -> FOUND in A.methods -> returns it = 3 calls, ending in a hit 3 (the init miss) + 3 (the whoAmI hit) = 6. WHY THIS WORKS AS AN ANSWER ------------------------------ The general rule this confirms: for a chain of depth D (the class itself plus however many superclasses it takes to either find the name or run out of chain), a SUCCESSFUL lookup at the k-th level costs exactly k calls, and a lookup that fails everywhere costs D calls -- the full chain, ending in None. Both of C's own lookups here happen to cost exactly 3, purely because A sits at the very bottom of a 3-level chain (C -> B -> A) and the class C() itself never defines init, making both scenarios walk the complete chain. A shorter inheritance depth, or a method defined higher up (closer to C), would cost fewer calls -- the cost genuinely scales with how far up the chain the answer (or the eventual "not found") lives.