3

I am trying to make a program that creates a list of lambda functions of the format y=mx+b, where 'm' and 'b' are predetermined values

My overall goal is to implement a function that

  • Takes a picture
  • Finds the lines on it
  • Extends them across the whole picture in a solid colour

Basically, something like a Hough transforms if you know what that is.

Once I have the lines for a specific image, I can create a lambda function to represent the slope of the line and where it begins. I'm having an issue not being able to append a lambda function to the list.

I have tried this :

if __name__ == "__main__":
  nums = []
  for i in range(10):
    j = lambda x: x + i
    nums.append(j)
  for i in nums:
    print(i(1))

Here is the error I'm getting :

Traceback (most recent call last):
  File "C:/Users/me/.PyCharmCE2018.3/config/scratches/scratch_3.py", line 7, in <module>
    print(i(1))
  File "C:/Users/me/.PyCharmCE2018.3/config/scratches/scratch_3.py", line 4, in <lambda>
    j = (lambda x: x + i)
TypeError: unsupported operand type(s) for +: 'int' and 'function'
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Kei
  • 53
  • 1
  • 5
  • Look here: http://book.pythontips.com/en/latest/lambdas.html – Mika72 Apr 12 '19 at 06:43
  • so i looked at that site and basically i tried changing the "i" to a "1" and that worked fine. why can'y i use "i" instead? for my real project i kind of need to have a changeable variable in there. – Kei Apr 12 '19 at 06:48
  • See https://sopython.com/canon/30/why-do-my-lambda-functions-or-nested-functions-created-in-a-loop-all-use-the-las/ – jonrsharpe Apr 12 '19 at 06:52
  • The specific error occurs because the name `i` used to iterate over the functions, is what will be looked up in the lambda. – Karl Knechtel Aug 19 '22 at 03:02

5 Answers5

7

The problem is that the lambdas you create are referring to the current value of i in the active stack frame. When you later reuse i for the second for loop, it is bound to the lambdas in your list. When invoked as i(1), the lambdas are trying to evaluate 1 + i where i is the lambda, so of course you get an error.

Probably what you want is to freeze the value of i at the point at which the lambda is created. You can do this by replacing:

j = lambda x: x + i

with:

j = (lambda y: lambda x: x + y)(i)

This effectively captures the current value of i by binding it to a lambda variable, then immediately applying that lambda, after which the binding remains fixed.

Tom Karzes
  • 22,815
  • 2
  • 22
  • 41
  • 2
    Or `lambda x, i=i: x + i` – jonrsharpe Apr 12 '19 at 06:51
  • @jonrsharpe Yes, I think that would work as well, although it would allow for a second argument to be passed that overrides that captured value of `i`. – Tom Karzes Apr 12 '19 at 06:53
  • ok i dont mean to sound stupid but how could i apply that to a lambda that needs 2 variables? like my mx+b function, where x is the thing that changes, and b and m are already declared but will change with every iteration in the for loop? – Kei Apr 12 '19 at 07:02
  • As an example, if you want to capture 3 values, say `i1`, `i2`, and `i3`, then you could do: `j = (lambda y1, y2, y3: lambda x: x + y1 + y2 + y3)(i1, i2, i3)`. Of course, you could do whatever you want with them in the inner lambda, but you get the idea. – Tom Karzes Apr 12 '19 at 07:03
  • ```if __name__ == "__main__": nums = [] j = 0 for i in range(10): j += 1 H = (lambda y: lambda x: y)(j) L = (lambda y: lambda x: H(x) + y)(i) nums.append(L) for i in nums: print(i(1)) – Kei Apr 12 '19 at 07:07
5

This will give you a clue:

>>> i=1
>>> a=lambda x:x+i
>>> a(5)
6
>>> i=2
>>> a(5)
7

lambda uses i in the outer scope. In the OP case, all the functions are the same. Using i in the final loop makes i a function, not an integer. Change it to something else, and you'll find the functions are all the same, using the last value of i:

nums = []
for i in range(10):
    j = lambda x: x + i
    nums.append(j)
for f in nums:
    print(f(1))
10
10
10
10
10
10
10
10
10
10

The fix is, make i a parameter to the function, to capture the value as a local variable:

nums = []
for i in range(10):
    j = lambda x,i=i: x + i
    nums.append(j)
for f in nums:
    print(f(1))
1
2
3
4
5
6
7
8
9
10
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
0

Your value of i has changed and it's not what you think.

First you create lambda:

j = lambda x: x + i

in hope, that i will remain as CURRENT value (so 0, 1, 2 and so on).

Then you execute it:

print(i(1))

Do you see, how you named your second iterator variable i? Change it to j and your example will work. Why? Because python resolves value of i in your lambda, when you execute it, not when you define it. So when you execute your lambda (i(1)) it will go to your lambda body and try x + i. Then it will lookup i, which now contains your lambda (not the INTEGER!). Hence your problem.

You need to do double function to make it work properly. Try this:

if __name__ == "__main__":
  nums = []
  for i in range(10):
    def generate_lambda(i):
      return lambda x: x + i
    j = generate_lambda(i)
    nums.append(j)
  for i in nums:
    print(i(1))

Why does this work? When you call generate_lambda, there will be i variable with your INTEGER value. It will shadow variable i used later on to iterate over lambdas. And since you never modify i variable inside generate_lambda function, it will stay like this forever.

Radosław Cybulski
  • 2,952
  • 10
  • 21
0

I think you need to learn something more about lambda functions... Actually, it's syntax is like : [lambda arguments: expression] So, the issue is you have two variables in the expression, so you need to pass two arguments. I don't really get what you want to achieve by this function, but I guess you need to have two arguments for m and b.

In your code, you need to initialize x and pass it as an argument to lambda.

            nums = []
            x=0
            for i in range(10):
                j = lambda x,i : x + i
                nums.append(j)
            for i in nums:
                print(i(1,1))
Sanket Patel
  • 227
  • 1
  • 14
0

You can use operator.add and functools.partial and do not lambda at all:

import operator
import functools


if __name__ == "__main__":
    nums = []

    for i in range(10):
        nums.append(functools.partial(operator.add, i))
    for i in nums:
        print(i(1))
Andrei Berenda
  • 1,946
  • 2
  • 13
  • 27