1

Here is what I tried:

quadrant = [lambda x, y: (sign[0] * x, sign[1] * y)
    for sign in ((1, -1), (1, 1), (-1, 1), (-1, -1))]

What I expected to get from this line was a list of functions that each return the input values with some set of signs applied. For example, quadrant[1](x, y) -> (x, y), quadrant[2](x, y) -> (-x, y), and so on.

What I actually got is a list of four identical functions, all the last function that I put into the list. For example, quadrant[1](x, y) -> (-x, -y), quadrant[2](x, y) -> (-x, -y), and so on.

What am I misunderstanding here? Why does each new function added to the list replace all the previously added functions?

1 Answer 1

1

You must break out of the closure:

quadrant = [ (lambda sign: lambda x, y: (sign[0] * x, sign[1] * y) ) (sign)
    for sign in ((1, -1), (1, 1), (-1, 1), (-1, -1))]

Or with named functions as Blender pointed out:

def wrapper (sign):
    def makeQuadrant (x, y):
        return (sign [0] * x, sign [1] * y)
    return makeQuadrant

quadrant = [wrapper (sign) for sign in ((1, -1), (1, 1), (-1, 1), (-1, -1))]

Or with a default named parameter as chepner pointed out:

quadrant = [lambda x, y, sign = sign: (sign[0] * x, sign[1] * y)
    for sign in ((1, -1), (1, 1), (-1, 1), (-1, -1))]

And now to answer your question:

Why does each new function added to the list replace all the previously added functions?

No function is replaced, your list contains four distinct functions as you can see here:

quadrant = [lambda x, y: (sign[0] * x, sign[1] * y, id (sign) )
    for sign in ((1, -1), (1, 1), (-1, 1), (-1, -1))]

for q in quadrant:
    print (id (q) )

What happens is that the value of sign is looked up when the function is called. The current value of sign is the last one assigned during the list comprehension, which is (-1, -1). Which can be seen here:

quadrant = [lambda x, y: (sign[0] * x, sign[1] * y, id (sign) )
    for sign in ((1, -1), (1, 1), (-1, 1), (-1, -1))]

for q in quadrant:
    print (q (2, 3) )

You might want to look a bit into "scoping" and "closures" for further reading.

Your example basically boils down to this:

fs = [lambda: x for x in range (10) ]
for f in fs: print (f () )
Sign up to request clarification or add additional context in comments.

6 Comments

I think at this point a named helper function would make the code a little more readable.
Or simply make sign an unused parameter with a default value: lambda x, y, sign=sign: ...
@chepner Although without doubt your approach is the neatest, I fear that it is the most difficult to understand.
@Hyperboreus a name is useful in the stacktrace if it ever throws an exception.
@Hyperboreus, Thanks. I also appreciate chepner's fix, esp. with a small clarification: ...sign=dflt_sign: (sign[0]...for dflt_sign in .... The scope structure is clear in your explication of Blender's fix, but if straightforward readability is the goal, one may as well just write out quadrant = [lambda x, y: (1, -1), lambda x, y: (1, 1), ...]. I'm still curious how the list [lambda: x for x in range(10)] has 10 functions in it, which seems to mean that the for-loop was applied to each function, but only 1 value for x, which seems to mean that the for-loop also applied to x alone. No?
|

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.