Python – Global variables and class methods

Ok, so i am nearly losing my marbles over this one.

I’m developing a small app that takes questions from an input XML file, displays them on the screen, takes the answers and writes it back on an output XML, using tkinter for the GUI and ElementTree for the XML handling. Each question might have multiple checks and subquestions, which show up dinamically on their respective frames as needed.

The main window is created by main as a MainWindow class instance (which contains all the tkinter stuff and a bunch of useful methods), at the very end of the code:

def main():
    global app
    window = Tk()
    app = MainWindow(window)
    window.mainloop()

if __name__ == '__main__':
    main()

This should make ‘app’ a global that should be accessible from anywhere on the code, right?

…..Apparently not?

I have created the Question class, which stores the attributes extracted from the XML, handles the display of the text and creates a Check class for each element in the XML, and a Subquestion class for each element in the XML.

The Question class is as follows:

class Question:
    def __init__(self, element):
        self.element = element
        self.question_text = element.get('text')
        self.ok = element.get('ok')
        self.answer = element.get('r')
        self.comment = element.get('comment')
        
        if element.find('check') is None:
            #print(app)
            pass
        else:
            #print(app)
            for check in element.iter('check'):
                Check(check)
            
        for subq in element.iter('subquest'):
            SubQuestion(subq)

There’s also the Check class, which handles the creation of the checkboxes and the writing of their respective answers to the ElementTree:

class Check:
    def __init__(self, element):
        self.element = element
        self.check_text = element.get('text')
        self.answer = element.get('r')
        self.var = IntVar()
        
        self.create_check()
    
    def create_check(self):
        print('check created')
        print(app)
        Checkbutton(app.frameChk, text=self.check_text, variable=self.var, command=self.write_check).pack(side=LEFT)

    def write_check(self):
        self.element.set('r', self.var.get())
        print('check written')

So far, so good. It works as expected, the checkboxes appear and their values are submitted, no problems

BUT…have you noticed the create_check method in the Check class references app.frameChk when creating the checkbox? This is the Tk frame reserved for the checkboxes, created by MainWindow’s __init__ method. This create_check method accesses app with no problems whatsoever, even without the global keyword inside of it. I even added the print(app) just to make sure.

Sure enough, everytime a checkbox gets created, it prints <__main__.MainWindow object at 0x0000024CCC557BE0>" to the console.

BUT…in Question’s __init__ method (or anywhere else, for that matter), i can’t seem to access anything related to app, it throws classes_app.py", line 129, in __init__ - print(app) NameError: name 'app' is not defined

Why on earth? Those are two classes which inherit nothing from others, and are hierarchically ‘equals’, just two average classes in a program (actually, Question is the one that creates the Check instances!). And one recognizes the global while all others dont?

Most mind-boggling: the Check class does NOT have the global keyword in it, and it still works?

So far i have tried:

1 – Declaring app = MainWindow(window) outside the main function in the global scope. Nothing.

2 – Using the global keyword inside each and every function on the program to see if they get it. Nope.

3 – Putting the main function right at the very top of the code, so that would be the very first declaration of app, doesn’t seem to do much either.

4 – Any combination of above solutions and trial-and-error shenanigans.

5 – Sacrifing a motherboard to the code goddess of python globals.

Mind you, I’m quite new to python, so I might have understood something about globals horribly wrong, or not understood it altogether, but from what the docs say, a variable declared with the global keyword should be a global variable, and global variables are accessible from any and all funcions and methods in the code, right?

If you are asking why I need it so much for the app = MainWindow(window) to be global: there will be a global function that cleans up the widgets between each question:

def destroy_widgets(parent):
    for widget in parent.winfo_children():
        widget.destroy()

So inside the classes’ methods i would call it like destroy_widgets(app.frameChk), for example. This is how i first encountered the problem.

I’d post the entire code, but it is quite extensive with all those tkinter definitions. If you guys think it is necessary, sure I will.

Any help or insight would be greatly appreciated, and many thanks in advance, this community is gold!

Answer

Definition/lifetime of variables
Regarding this:

Most mind-boggling: the Check class does NOT have the global keyword in it, and it still works?

You only need the global-keyword for creating or changing global variables in a local context, see this answer.

In addition, the statement global app does not yet create the variable app – it is only created on first assignment. In your example, this means that app is only created here: app = MainWindow(window). If Question gets created before this statement (in your example, by Tk() or by MainWindow(window)), app does not exist yet.

You can try this out in an interpreter:

>>> global app
>>> print(app)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'app' is not defined

Namespacing

Another important fact to look out for: If your code is organized in different modules, you have to be aware of namespacing.

Example Let’s assume we have two python files: first.py and second.py:

Content of first.py:

app = 5

Content of second.py:

import first
print(first.app)

Running this works:

$ python3 second.py
5

Just putting print(app) would not work, because of the different modules.