The assignment in your loop is equivalent to:
def function(x):
return function(x) * fun(x)
Which is a recursive function without a stop condition. Your initial definition of function is shadowed, due to python's binding rules:
The following constructs bind names: formal parameters to functions,
import statements, class and function definitions (these bind the
class or function name in the defining block)
You can trace it and verify such behaviour using python3 -m trace --trace script.py :
--- modulename: script, funcname: <lambda>
script.py(6): function = lambda x: function(x) * fun(x)
--- modulename: script, funcname: <lambda>
script.py(6): function = lambda x: function(x) * fun(x)
# Repeated until a RecursionError is thrown
You can instead bind your function as an argument of the lambda:
fun = lambda x: x**2
function = lambda x: x**2
for i in range(2):
function = lambda x, y=function: y(x) * fun(x)
print(function(2))
Which will produce the following trace:
--- modulename: script, funcname: <module>
script.py(3): fun = lambda x: x**2
script.py(4): function = lambda x: x**2
script.py(5): for i in range(2):
script.py(6): function = lambda x, y=function: y(x) * fun(x)
script.py(5): for i in range(2):
script.py(6): function = lambda x, y=function: y(x) * fun(x)
script.py(5): for i in range(2):
script.py(8): print(function(2))
--- modulename: script, funcname: <lambda>
script.py(6): function = lambda x, y=function: y(x) * fun(x)
--- modulename: script, funcname: <lambda>
script.py(6): function = lambda x, y=function: y(x) * fun(x)
--- modulename: script, funcname: <lambda>
script.py(4): function = lambda x: x**2
--- modulename: script, funcname: <lambda>
script.py(3): fun = lambda x: x**2
--- modulename: script, funcname: <lambda>
script.py(3): fun = lambda x: x**2
64