Python variables are bound by name, and a for loop does not actually create a new scope. So when you do lambda c: v(c)
, you are actually creating a function that will look up v
from its surrounding scope when its being executed. This means that updates to v
are all applied when the function is executed.
In particular, it means that the following two definition already create an infinite loop:
v = lambda b: u(b)**2
u = lambda c: v(c)
Because v
calls u
, and u
calls v
. It does not matter that the values are updated later, since the value will be looked up when the function is called.
You can visualize this easily using the following:
>>> x = lambda: y
>>> y = 2
>>> x()
2
>>> y = 5
>>> x()
5
Even though the function x
is never updated, it will still use the updated value for y
.
What you need here is a closure to put get references to the original functions in a separate scope so that later changes do not affect the function. A simple way is to add another function where the function you want to call is passed as an argument. Since functions create new variable scopes, these will be then independent of the original definitions:
for i in range(0, 5):
print('Starting loop', i)
v = (lambda u: lambda b: u(b)**2)(u)
u = (lambda v: lambda c: v(c))(v)
print('Ending loop', i)
See also this question on how binding works and how closures help there.