Lambdas inside list comprehensions

后端 未结 5 1225
隐瞒了意图╮
隐瞒了意图╮ 2020-12-18 02:55

I wanted to have a list of lambdas that act as sort of a cache to some heavy computation and noticed this:

>>> [j() for j in [lambda:i for i in rang         


        
相关标签:
5条回答
  • 2020-12-18 03:36

    The problem is that you're not capturing the value of i on each iteration of the list comprehension, you're capturing the variable each time through.

    The problem is that a closure captures variables by reference. In this case you are capturing a variable whose value changes over time (as with all loop variables), so it has a different value when you run it than when you created it.

    0 讨论(0)
  • 2020-12-18 03:38

    The lambda returns the value of i at the time you call it. Since you call the lambda after the loop has finished running, the value of i will always be 9.

    You can create a local i variable in the lambda to hold the value at the time the lambda was defined:

    >>> [j() for j in [lambda i=i:i for i in range(10)]]
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    

    Another solution is to create a function that returns the lambda:

    def create_lambda(i):
        return lambda:i
    >>> [j() for j in [create_lambda(i) for i in range(10)]]
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    

    This works because there is a different closure (holding a different value of i) created for each invocation of create_lambda.

    0 讨论(0)
  • 2020-12-18 03:39

    I'm not sure if it's a bug or a feature, but what's happening is that lambda:i doesn't evaluate i before forming the lambda function. So, it's literally just a function which evaluates whatever the current value of i is. Here's another example of how this happens.

    >>> i=5
    >>> x=lambda:i
    >>> x()
    5
    >>> i=6
    >>> x()
    6
    

    So, obviously, what's happening is the same thing except that i is going to 9 in your examples as it's being assigned through the range 0 through 9 in that order.

    I don't think that there's really any good way to avoid it. Lambda functions in Python are pretty limited. It's not really a functional language at heart.

    0 讨论(0)
  • 2020-12-18 03:40

    Turn a square bracket into a parenthesis, like that: [j() for j in (lambda:i for i in range(10))] output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] Tuples are immutable variables

    0 讨论(0)
  • 2020-12-18 03:48

    What you're seeing here is the effect of closures. The lambda is capturing state from the program to be used later. So while each lambda is a unique object, the state isn't necessarily unique.

    The actual 'gotchya' here, is that the variable i is captured, not the value that i represents at that point in time. We can illustrate this with a much easier example:

    >>> y = 3
    >>> f = lambda: y
    >>> f()
    3
    >>> y = 4
    >>> f()
    4
    

    The lambda holds on to the reference to the variable, and evaluates that variable when you execute the lambda.

    To work around this, you can assign to a local variable within the lambda:

    >>> f = lambda y=y:y
    >>> f()
    4
    >>> y = 6
    >>> f()
    4
    

    Finally, in the case of a loop, the loop variable is only 'declared' once. Therefore, any references to the loop variable within the loop will persist past the next iteration. This includes the variable in list comprehensions.

    0 讨论(0)
提交回复
热议问题