Python: lambda function behavior with and without arguments?

I use lambda functions to program the GUI using tkinter. Recently, I got stuck when implementing buttons that open files:

self.file="" button = Button(conf_f, text="Tools opt.", command=lambda: tktb.helpers.openfile(self.file)) 

As you can see, I want to determine the path to a file that can be updated, and this is not known when creating the GUI. The problem was that before my code was:

 button = Button(conf_f, text="Tools opt.", command=lambda f=self.file: tktb.helpers.openfile(f)) 

The lambda function had a keyword argument to pass the file path. In this case, the f parameter was not updated when there was self.file .

I got the keyword argument from the code snippet and I use it everywhere. Obviously, I shouldn't ...

It’s still not clear to me ... Can someone explain to me the difference between the two forms of lambda and when to use each other?

Thanks!

PS: The following comment led me to a solution, but I would like to get a little more explanation: lambda works weird with tkinter

+4
source share
1 answer

I will try to explain this in more detail.

If you do

 i = 0 f = lambda: i 

you create a function (lambda is essentially a function) that accesses its encompassing region i variable.

Inside, he does this by having a so-called closure that contains i . It is, loosely speaking, a kind of pointer to a real variable that can hold different values ​​at different points in time.

 def a(): # first, yield a function to access i yield lambda: i # now, set i to different values successively for i in range(100): yield g = a() # create generator f = next(g) # get the function f() # -> error as i is not set yet next(g) f() # -> 0 next(g) f() # -> 1 # and so on f.func_closure # -> an object stemming from the local scope of a() f.func_closure[0].cell_contents # -> the current value of this variable 

Here, all values ​​of i are - at one time - stored in this closure. If the function f() needs them. he gets them from there.

You can see this difference in the disassembly lists:

These functions a() and f() disassembled as follows:

 >>> dis.dis(a) 2 0 LOAD_CLOSURE 0 (i) 3 BUILD_TUPLE 1 6 LOAD_CONST 1 (<code object <lambda> at 0xb72ea650, file "<stdin>", line 2>) 9 MAKE_CLOSURE 0 12 YIELD_VALUE 13 POP_TOP 3 14 SETUP_LOOP 25 (to 42) 17 LOAD_GLOBAL 0 (range) 20 LOAD_CONST 2 (100) 23 CALL_FUNCTION 1 26 GET_ITER >> 27 FOR_ITER 11 (to 41) 30 STORE_DEREF 0 (i) 33 LOAD_CONST 0 (None) 36 YIELD_VALUE 37 POP_TOP 38 JUMP_ABSOLUTE 27 >> 41 POP_BLOCK >> 42 LOAD_CONST 0 (None) 45 RETURN_VALUE >>> dis.dis(f) 2 0 LOAD_DEREF 0 (i) 3 RETURN_VALUE 

Compare this to the b() function, which looks like

 >>> def b(): ... for i in range(100): yield >>> dis.dis(b) 2 0 SETUP_LOOP 25 (to 28) 3 LOAD_GLOBAL 0 (range) 6 LOAD_CONST 1 (100) 9 CALL_FUNCTION 1 12 GET_ITER >> 13 FOR_ITER 11 (to 27) 16 STORE_FAST 0 (i) 19 LOAD_CONST 0 (None) 22 YIELD_VALUE 23 POP_TOP 24 JUMP_ABSOLUTE 13 >> 27 POP_BLOCK >> 28 LOAD_CONST 0 (None) 31 RETURN_VALUE 

The main difference in the loop

  >> 13 FOR_ITER 11 (to 27) 16 STORE_FAST 0 (i) 

in b() vs.

  >> 27 FOR_ITER 11 (to 41) 30 STORE_DEREF 0 (i) 

in a() : STORE_DEREF is stored in the cell object (closure), and STORE_FAST uses a "normal" variable, which (maybe) works a little faster.

Lambda also matters:

 >>> dis.dis(lambda: i) 1 0 LOAD_GLOBAL 0 (i) 3 RETURN_VALUE 

Here you have LOAD_GLOBAL , and the above example uses LOAD_DEREF . The latter also applies to closure.

I completely forgot about lambda i=i: i .

If you have a value as the default parameter, it finds its path in the function through a completely different path: the current value of i is passed to the newly created function through the default parameter:

 >>> i = 42 >>> f = lambda i=i: i >>> dis.dis(f) 1 0 LOAD_FAST 0 (i) 3 RETURN_VALUE 

Thus, the function is called as f() . It detects the absence of an argument and fills the corresponding parameter with a default value. All this happens before the function is called; from inside the function you see that the value is taken and returned.

And there is another way to accomplish your task: just use the lambda as if it had taken the value: lambda i: i . If you call it, he complains about the missing argument.

But you can handle this with functools.partial :

 ff = [functools.partial(lambda i: i, x) for x in range(100)] ff[12]() ff[54]() 

This shell receives the called number and the number of arguments to be passed. The resulting object is a callable that calls the original callable with these arguments plus any arguments you give it. It can be used here to save a locked value intended for this.

+7
source

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


All Articles