The algorithm you describe behaves in a manner known as dynamic scoping, where non-local names are taken from the caller's environment at run time. But Python does not use dynamic scoping. It uses lexical scoping. That means non-local names are resolved from the lexically surrounding function. For example, the function returned from this function f
:
def f():
x = 5
def g():
print(x)
return g
... always prints 5, even if it's called like this:
def h():
g = f()
x = 10
g()
g
is called a closure. Which names are non-local, and how "far up" they refer (you can have a function inside a function inside a function), is decided by the bytecode compiler, not at runtime. The bytecode contains all this information, so at runtime, the interpreter does not search through anything, much less multiple frames, while resolving the names. It always knows exactly where to look, from the kind of bytecode instruction used.
Addendum: The reason frame objects exist, have the names and values of all locals, and refer to their caller's frames, is unrelated. The locals are stored to facilitate debugging (and some nasty hacks that involve affecting the locals of calling functions). f_back
exists is debugging and dark magic too, most prominently it is necessary for stack traces to work.