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?
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.
__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
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!
Definition/lifetime of variables
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).
Question gets created before this statement (in your example, by
Tk() or by
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
Another important fact to look out for: If your code is organized in different modules, you have to be aware of namespacing.
Let’s assume we have two python files:
app = 5
import first print(first.app)
Running this works:
$ python3 second.py 5
print(app) would not work, because of the different modules.