ENPM ENPM - 1 year ago 49
Python Question

Python tkinter NameError when using lambda command

I am new to Python's

module so I would be grateful for any help, even an explanation as to why this is simply not possible (if that is the case).

I have a list of 4-tuples of the form
(foo, bar, x, y)
; something like this:

TUPLE_LIST = [('Hello', 'World', 0, 0),
('Hovercraft', 'Eels', 50, 100),

and a
loop later which ideally instantiates the variable
as a button with the text
, with each button having a function to add its respective
to an already defined
widget, then places it at the co-ordinates
x, y

for foo, bar, x, y in TUPLE_LIST:
exec("{0} = Button(self, text='{1}', command=lambda: self.update_text('{1}'))".format(foo, bar))
eval(name).place(x=x, y=y)

The buttons place perfectly, but if I go to click on one of the them, I get the following error:

Traceback (most recent call last):
File "...\lib\tkinter\__init__.py", line 1550, in __call__
return self.func(*args)
File "<string>", line 1, in <lambda>
NameError: name 'self' is not defined

I'm guessing this has something to do with the fact that the command is defined with a lambda, and thus isn't a defined 'function', per se, and yet I have seen other people define their buttons' commands with lambdas. So is it something to do with using
as well? Also, here's the code for
(if it matters):

def update_text(self, new_char):
old_str = self.text_box_str.get()
# Above is a StringVar() defined in __init__ and used as textvariable in Entry widget creation.
self.text_box_str.set(old_str + new_char)

Any help would be much appreciated. Thanks in advance.

Answer Source

This is a very bad idea. There's simply no reason to dry to dynamically create variable names. It makes your code more complex, harder to maintain, and harder to read.

If you want to reference widgets by name, use a dictionary:

buttons = {}
for foo, bar, x, y in TUPLE_LIST:
    buttons[foo] = Button(self, text=bar, ...)

As for the problem with self, there's not enough code to say what the problem is. If you're using classes, the code looks ok. If you aren't, you shouldn't be using self.

To create a function that updates a widget, you can simply pass that widget or that widget's name to the function. It's not clear what widget you're wanting to update. It appears to be an entry widget. I can't tell if you have one entry widget that all buttons must update, or one entry per button. I'll assume the former.

In the following example I'll show how to pass to a function the variable to be changed along with the text to add. This solution doesn't use the textvariable attribute, though you can if you want. Just pass it rather than the widget.

buttons[foo] = Button(..., command=lambda widget=self.text_box, value=bar: self.update_text(widget, value))
def update_text(self, widget, value):
    old_value = widget.get()
    new_value = old_value + value
    widget.delete(0, "end")
    widget.insert(0, new_value)

Of course, if all you're doing is appending the string, you can replace those four lines with widget.insert("end", value)