ppizarror / pygame-menu

A menu for pygame. Simple, and easy to use
https://pygame-menu.readthedocs.io/
Other
555 stars 141 forks source link

Looping to add different values to a Button callback #362

Closed JKornberg closed 3 years ago

JKornberg commented 3 years ago

Environment information Describe your environment information, such as:

Describe the bug Looping to add buttons to a page and adding the index as an argument in the callback overrides all buttons with the last button's value.

To Reproduce In your menu do

 for i in range(10):
     menu.add.button("Download",lambda: run(i))

def run(i):
     print(i)

Expected behavior It should print out each index as you press different buttons. Instead it only prints out the last added button

Additional context I also tried deep copying i and it still had the same result.

ppizarror commented 3 years ago

Hi!

That error is from a Python misuse of variables defined within lambdas. For each lambda you've created, the i parameter is being read from a global scope, so, after the iteration of range the last value of the variable will be used, in that case, 9; even you can modify the value to set any object:

for i in range(10):
  menu.add.button("Download", lambda: run(i))
i = "I am the best hacker"
# If any button is pressed, the hacker string will be printed

To avoid such kinds of errors, you must invoice which value are you supposed to execute for each lambda. A simple but elegant solution is to provide the i value before lambda execution as a parameter input:

for i in range(10):
  menu.add.button("Download", lambda i=i: run(i))

That solves the issue. You also can use partial from functools library, but modern lambdas let do many things nowadays, partial was required for early Python versions.

References: https://stackoverflow.com/questions/11723217/python-lambda-doesnt-remember-argument-in-for-loop

JKornberg commented 3 years ago

You are absolutely right. Thank you I am closing the issue