Is this bad programming practice in tkinter?

I'm learning to write event driven programs using tkinter and am using Fredrik Lundh's excellent tutorial. In that, he mentions that it's better to define a class (App) for the frame and run the program as an instance of the class instead of just starting it up thus:

root = Tk()
w = Label(root, text = 'hello, world!')
w.pack()
root.mainloop()

I have 3 questions:

  1. Is it bad programming practice to do it in this simpler way?

  2. If I do define a class, with callback functions bound to the widgets, do the functions all have to be within the class itself? i.e. Can I have a button that says go within the class, which when I click runs an elaborate routine that's been defined outside the class?

  3. Is it bad practice to then take the result generate outside the class and display it within?

Essentially, I can make the program work by jumping in and out of the class but am not sure if it's bad practice to do so (a la global variables).


Solution 1:

  1. You will want to use classes as your application gets larger. Instead of having to wrap your mind around the entire code, you can concentrate on a class at a time.
  2. You are not restricted to using only the methods in a class. Your code may utilize external functions or classes to get information or even modify arguments given to them.
  3. No, that is how would probably display the information. Alternatively, you might use a file to output your results, and it may be possible to print to the console if it is present.

Example:

import tkinter
import random

class Application(tkinter.Frame):

    @classmethod
    def main(cls):
        root = tkinter.Tk()
        frame = cls(root)
        frame.grid()
        root.mainloop()

    def __init__(self, master=None, cnf={}, **kw):
        super().__init__(master, cnf, **kw)
        self.w = tkinter.Label(self, text='Hello, world!')
        self.w.grid()
        self.v = tkinter.Button(self, text='Press Me', command=self.click)
        self.v.grid()
        self.u = tkinter.Button(self, text='Me Too!',
                                command=lambda: external_mutator(self.w))
        self.u.grid()

    def click(self):
        self.w['text'] = external_function(3)

def external_function(ndigits):
    return round(random.random(), ndigits)

def external_mutator(widget):
    widget['text'] = external_function(6)
    print('Hello to you too!')  # shown on console if present

if __name__ == '__main__':
    Application.main()

Alternative to the main classmethod:

import tkinter
import random

class Main(tkinter.Tk):

    def __init__(self, screenName=None, baseName=None, className='Tk',
                 useTk=1, sync=0, use=None):
        super().__init__(screenName, baseName, className,
                         useTk, sync, use)
        frame = Application(self)
        frame.grid()
        self.mainloop()

class Application(tkinter.Frame):

    def __init__(self, master=None, cnf={}, **kw):
        super().__init__(master, cnf, **kw)
        self.w = tkinter.Label(self, text='Hello, world!')
        self.w.grid()
        self.v = tkinter.Button(self, text='Press Me', command=self.click)
        self.v.grid()
        self.u = tkinter.Button(self, text='Me Too!',
                                command=lambda: external_mutator(self.w))
        self.u.grid()

    def click(self):
        self.w['text'] = external_function(3)

def external_function(ndigits):
    return round(random.random(), ndigits)

def external_mutator(widget):
    widget['text'] = external_function(6)
    print('Hello to you too!')  # shown on console if present

if __name__ == '__main__':
    Main()

Solution 2:

  1. in general yes... for smaller programs it may not be too problematic but as complexity grows it is nice to have things consolidated into classes
  2. yes
  3. not really ... (globals tend to be bad practice...) but what you describe is message encapsulation and passing

Solution 3:

1) I would say for this example, it's not necessarily wrong. The program runs. The real worry is that as you start to make more complex programs, you may want a more structured format. Classes can help.

2) The functions do not all have to be within the class itself. Methods may call outside functions. The main reason to have a method as opposed to a function is that the method has ready access to all of the object's attributes. You want to avoid a function reaching inside an object to manipulate the attributes.

3) It's better to pass in variables to a class than to define globals, mostly because it can get difficult to maintain scope.