1

I was searching Stack Overflow for a solution to a problem of mine when I stumbled upon this user submitted solution (to someone else's question):

appsMenu.add_command(label=app, command=openApp(Apps[app]))

Command parameters that call functions need to be wrapped in a lambda, to prevent them from being called right away. Additionally, commands bound within a for loop need the looping variable as a default argument, in order for it to bind the right value each time.

 appsMenu.add_command(label=app, command=lambda app=app: openApp(Apps[app]))

As you can see, the user wrote lambda app=app, and for the love of me I cannot figure out why. What does it do?

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
Kevin
  • 11
  • 1
  • This is a duplicate of an _answer_: https://stackoverflow.com/a/15839049/8586227. – Davis Herring Apr 02 '18 at 01:26
  • "What does it do?" It causes early binding, because of how functions are compiled, as explained in the first linked duplicate. Using early binding avoids problems related to late binding, which is the default, as explained in the second linked duplicate. (Was something unclear about the description in the post? "Additionally, commands bound within a for loop need the looping variable as a default argument, in order for it to bind the right value each time." `app=app` is exactly such "looping variable as a default argument".) – Karl Knechtel Aug 20 '22 at 01:45

1 Answers1

1

The lambda expression is used to define a one-line (anonymous) function. Thus, writing

f = lambda x: 2*x

is (more or less) equivalent to

def f(x):
    return 2*x

Further, as for normal functions, it is possible to have default arguments also in a lambda function. Thus

k = 2
f = lambda x=k: 2*x

can be called either as f() where x will be the value of k i.e. 2 or as f(3) where x = 3. Now, to make it even more strange, since the lambda function has its own name space, it is possible to exchange the k with x as

x = 2
f = lambda x=x: 2*x

which then means that the default value of f() will be the (outer) x value (i.e. 2) if no other parameter is set.

Thus, the expression

lambda app=app: openApp(Apps[app])

is a function (unnamed) that takes one argument app with the default value of the (outer) variable app. And when called, it will execute the instruction openApp(Apps[app]) where Apps is a global (outer) object.

Now, why would we want that, instead of just writing command=openApp(Apps[app]) when calling .add_command()? The reason is that we want the command to execute not when defining the command, i.e., when calling .add_command(), but at a later time, namely when the menu item is selected.

Thus, to defer to the execution we wrap the execution of openApp(Apps[app]) in a function. This function will then be executed when the menu item is selected. Thereby we will call openApp then, instead of right now (at the time we want to add the command) which would be the case otherwise.

Now, since the lambda function is called with no arguments, it will use its default parameter (outer) app as (inner) app. That makes the whole argument a bit redundant, as the outer variable is directly accessible within the lambda expression. Therefore, it is possible to simplify the expression to lambda: openApp(Apps[app]) i.e. wiht no arguments, making the full line

appsMenu.add_command(label=app, command=lambda: openApp(Apps[app]))

To me, this looks a bit simpler, and is the way I would write it, but conceptually there is no difference between this and the code with default parameters.

JohanL
  • 6,671
  • 1
  • 12
  • 26