Please help me understand this code behind the scenes:

def someFunction(): list = [] for i in range(5): list.append(lambda x: i ** x) return list #вызов функции list = someFunction() print(list[1](2)) 

In theory, the result of this code should be one, but in fact 16. is displayed. I do not quite understand how this program works. To my mind:

The compiler starts with the 1st line. See def and skip the entire function. Then gets to the string

list = someFunction ()

after which begins to interpret the function someFunction . It gets to the for loop, and the first iteration begins. But the command that adds an element to the end of the list takes a function as an argument, which, in turn, has not yet received its argument. What is added to the list? I assume that a function object has been created that is associated with a specific list item. So, I think, happens at all iterations. After that, the function returns a list containing references to the created function objects. If everything is as it really is, then this is logical.

But what happens when reading an item from the list? Okay. If it comes up 16 instead of 1, most likely, all the objects of the functions take 4-ku as an iteration variable, i.e. last iteration. And here it is not entirely clear why when creating the function object the value of the loop counter was not saved? I suppose that you can tell me: "And how do you think this value will remain if it is not in the parameters of the lambda function?". But then how does she find and substitute the last iteration? And in general, while writing this post is even more confused: why would this compiler create an object function before this function is called? After all, the object of the function someFunction was constructed when we called this function. And the objects of the lambda function were created before they were called.

Be kind, help sort out this issue. Thank.

  • one
    And here it is not quite clear why the value of the loop counter was not preserved when creating the function object - it also refers to the variable i, which after all iterations remained equal to 4. As far as I understand, this is a mechanism identical to javascript closures, which can be resolved by another nested lambda, inside which i will remain constant. - etki
  • Python FAQ: Why do I need it? - jfs

2 answers 2

The variable in your example will always be equal to 4 because the search for a variable in the ambient scope (according to the LEGB rule) occurs at the moment of calling the nested function, and not when creating the object. Those. inside the lambda function i is "a reference to i in the enclosing region", and not a "reference to int(0) " or "reference to int(4) ". The example from the answer @ aleks.andr with the default argument will work because the calculation of the default value occurs once at the moment of creating the nested function (which, by the way, can also lead to interesting effects if you create a mutable-object and change it inside the function: def f(a=[]): a.append(0) ; print(a) )

PS Do not overlap the builtin list in your code, this can lead to surprising consequences.

    And here it is not entirely clear why the value of the loop counter was not preserved when creating the function object - it also refers to the variable i, which after all iterations remained equal to 4.

    You really need to capture the variable i when declaring a lambda. Here is the working code that returns your unit:

     def someFunction(): list = [] for i in range(5): list.append(lambda x,i=i: i ** x) return list #вызов функции list = someFunction() print(list[1](2))