1

I want to create list of lambdas by a given size n (so manual assignment won't work) where each element depends on previous function and it index in the list.

I've tried various stuff like this :

n = 3
func_list = [lambda x : 1]
for k in range(1,n):
    func_list.append(lambda x : func_list[k-1](x) * (x+k))
print(list(map(lambda f : print(f(1)), func_list)))

But in all attempts I've got error message about recursion depth:

RecursionError                            Traceback (most recent call last)
<ipython-input-296-f35123a830c4> in <module>
      2 for k in range(1,3):
      3     func_list.append(lambda x : func_list[k-1](x) * (x+k))
----> 4 print(list(map(lambda f : print(f(1)), func_list)))

<ipython-input-296-f35123a830c4> in <lambda>(f)
      2 for k in range(1,3):
      3     func_list.append(lambda x : func_list[k-1](x) * (x+k))
----> 4 print(list(map(lambda f : print(f(1)), func_list)))

<ipython-input-296-f35123a830c4> in <lambda>(x)
      1 func_list = [lambda x : 1]
      2 for k in range(1,3):
----> 3     func_list.append(lambda x : func_list[k-1](x) * (x+k))
      4 print(list(map(lambda f : print(f(1)), func_list)))

 ... last 1 frames repeated, from the frame below ...

<ipython-input-296-f35123a830c4> in <lambda>(x)
      1 func_list = [lambda x : 1]
      2 for k in range(1,3):
----> 3     func_list.append(lambda x : func_list[k-1](x) * (x+k))
      4 print(list(map(lambda f : print(f(1)), func_list)))

RecursionError: maximum recursion depth exceeded
3
  • What kind of function you are expecting from the lambdas? Since in the for loop you are actually executing the lambda function. See herefunc_list[k-1](x) Commented Sep 8, 2019 at 20:43
  • 1
    Also, you might want to spend some time on the late binding of closures (lambdas): e.g. read this Commented Sep 8, 2019 at 20:49
  • 1
    You had this behavior because because all method use the same reference K, and k == 2, so every k - 1 will return the same result in all method so you keep returning the same element until you got the error Commented Sep 8, 2019 at 21:30

2 Answers 2

2

As Elegant Odoo's answer pointed out, Python lambdas capture variables by reference.

Another term that you might hear when searching for answers is "late binding", which means binding of the variable inside the lambda to its actual value happens only when the lambda is executed. As a results, all lambda functions you've created has the same (and final) value for k, which causes infinite recursion.

While the exec-based approach is valid, I think most people would agree that exec should be avoided in practical code. An alternative solution to this is to create lambda functions via functions to force early binding:

n = 3
func_list = [lambda x: 1]

def create_fn(k_val):
    return lambda x: func_list[k_val - 1](x) * (x + k_val)

for k in range(1, n):
    func_list.append(create_fn(k))

print(list(map(lambda f: f(1), func_list)))

When you call create_fn with k, the current value of k is passed to the function under the name k_val. The variable k_val in the lambda function is then bound to the k_val variable in the scope of create_fn, with the value of k at the time when it's called.

Sign up to request clarification or add additional context in comments.

2 Comments

"by reference" is really not standard terminology here. Python uses lexically scoped, late-binding closures.
I agree, which is why I used "late binding" in the explanation. However, I do believe that "reference vs. value" is a common topic in programming languages, and it might help to at least use it as an analogy.
1

A very good case to explain the difference between Reference and Value:

Let me explain why this happen to understand what happen lets use a normal function so we can debug the value of K:

    n = 3
    func_list = [lambda x : 1]
    for k in range(1,n):
        def f(x):
            print('K is: ', k)
            return func_list[k-1](x) * (x + k)
        func_list.append(f)

    # just print will be enough to cause error
    print(func_list[2])

Out puts is:

    K is : 2
    K is : 2
    K is : 2
    K is : 2
    K is : 2
    ....
    ....
    ....
    ....
    RecursionError: maximum recursion depth exceeded while calling a Python object

You are using the same variable (reference to value) in all methods. K reference to value 2 generated from the loop.

to demonstrate after the end of loop make k = 5, this will raise error index out of range.

A simple solution with exec :

    n = 3
    func_list = [lambda x: 1]
    for k in range(1, n):
        # here you are not using variable k we are using the value it's like
        # we are rewriting it to code every time but is not you who typing it's the compute -_^
        exec('func_list.append(lambda x : func_list[{k}-1](x) * (x + {k}))'.format(k=k))

    # lambda print(f(1)) will create [None, None, None] I removed the print
    print(list(map(lambda f: f(1), func_list)))

Out put:

    [1, 2, 6]

Of curse the solution above work for your case because K is jut a simple Integer what if K is an very complicated object, with complicated attributes and private variable. In order to achieve the same thing you need to reconstruct the object.

    # we need to consract another object using the value
    exec('... KClass({}, {}, {}) ...'.format(k.attr1, k.attr2, k.attr2))

Imaging that KClass uses attribute Object of XClass you will need to construct both object k and x.

SO using exec is not practical a better way to handle this is the Factory Pattern:

    n = 3
    func_list = [lambda x: 1]


    def method_factory(k):
        # here K reference it's not the same K reference that is created
        # in loop there are different but with the alias K
        return lambda x: func_list[k - 1](x) * (x + k)


    for k in range(1, n):
        func_list.append(method_factory(k))

    print(list(map(lambda f: f(1), func_list)))

Why this work because when you call method_factory this will create its own scope that contain a reference with alias K that point to the same value that reference K of loop points to.

Hope you got the idea it's all about reference and scopes.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.