0

I am new to Python's tkinter 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),
                  etc.]

and a for loop later which ideally instantiates the variable foo as a button with the text bar, with each button having a function to add its respective bar to an already defined Entry 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 exec as well? Also, here's the code for update_text (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.

1 Answer 1

2

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, ...)
    buttons[foo].place(...)

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)

Sign up to request clarification or add additional context in comments.

3 Comments

OK, fair enough, but that still doesn't solve the original problem. How would I then create those buttons in such a way such that each one has a command that adds its own bar to the Entry widget? Because I can't think of any way to do that other than string formatting in an exec call, and that's what seemed to be causing the problem. And yes, I am using classes (but note that the self that was causing problems wasn't even one of mine).
@ENPM: There's virtually never a case where you should resort to calling exec on a constructed string. I've updated my answer to show how to call a function and pass it the widget that is to be updated. The function doesn't need to know the name of the widget, it can be given a reference to the widget itself.
Hi, @BryanOakley your idea is really great. I appreciate your help.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.