Strange lambda behavior in loops

I came across python behavior that is hard for me to understand. This is the code for the evidence concept:

from functools import partial if __name__ == '__main__': sequence = ['foo', 'bar', 'spam'] loop_one = lambda seq: [lambda: el for el in seq] no_op = lambda x: x loop_two = lambda seq: [partial(no_op, el) for el in seq] for func in (loop_one, loop_two): print [f() for f in func(sequence)] 

The conclusion above:

 ['spam', 'spam', 'spam'] ['foo', 'bar', 'spam'] 

The behavior of loop_one surprising to me, since I expect it to behave like loop_two : el is an immutable value (string) that changes in each loop, but lambda seems to hold a pointer to a "cyclic variable" , for example, if the loop will process the same memory address for each element of the sequence.

The above behavior is the same with full-blown functions with a for loop in them (therefore, it is not the syntax for defining a list).

But wait: more ... and more mysterious!

The following script works like loop_one :

 b = [] for foo in ("foo", "bar"): b.append(lambda: foo) print [a() for a in b] 

(output: ['bar', 'bar'] )

But look what happens if you replace the variable name foo with a :

 b = [] for a in ("foo", "bar"): b.append(lambda: a) print [a() for a in b] 

(output: [<function <lambda> at 0x25cce60>, <function <lambda> at 0x25cced8>] )

Any idea what is going on here? I suspect there should be some information related to the underlying C implementation of my interpreter, but I have nothing (Jthon, PyPy or the like) to check if this behavior is compatible across different implementations.

+6
source share
2 answers

The lambda: el function used in loop_one refers to the variable el , which is not defined in the local scope. So Python is looking for it further in the encompassing area of ​​another lambda :

 lambda seq: [lambda: el for el in seq] 

in accordance with the so-called LEGB rule.

By the time lambda: el was called, that encompassing lambda (of course) had already been called and the list rating had been evaluated. el , used in list comprehension, is a local variable in this enclosing lambda. Its value is the one that is returned when Python searches for the value of el in lambda: el . This value for el is the same for all the various lambda: el functions lambda: el in list comprehension: this is the last value assigned to el in the for el in seq loop. So el always 'spam' , the last value in seq .


You have already found one workaround to use closure, for example loop_two . Another way is to define el as a local variable with a default value:

 loop_one = lambda seq: [lambda el=el: el for el in seq] 
+4
source

Variables ( foo in the following example) are not bound when creating lambda, but when calling lambda.

 >>> b = [] >>> for foo in ("foo", "bar"): ... b.append(lambda: foo) ... >>> foo = "spam" >>> print [a() for a in b] ['spam', 'spam'] 

 >>> b = [] >>> for foo in ("foo", "bar"): ... b.append(lambda foo=foo: foo) ... >>> print [a() for a in b] ['foo', 'bar'] 
+3
source

Source: https://habr.com/ru/post/957340/


All Articles