0

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!

1 Answers1

0

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.

  • Thanks for the answer! Well, the `Question` instances are created by a certain `show_question` method in `MainWindow`, so from what I understand this assumption that Question is created by `Tk()` would not be correct. Maybe I could use a 'proxy' global variable, like `global app` `xxx = MainWindow(window) ` `app = xxx` maybe? Doesnt seem to work, but maybe there is a way somewhere along this path? – gbertonzzin Mar 11 '21 at 13:13
  • 1
    Are the `Question` instances created by the `__init__`-Method of `MainWindow`? In that case, the same logic holds: `app` is only assigned after `MainWindow` is successfully created, so it can't be accessed before creation (the `__init__`-method) finishes. – Thomas Lemberger Mar 11 '21 at 13:17
  • 1
    Maybe a better question: do you really have to access `app` in the `__init__`-method of `Question`? Maybe this was just a "wrong" way of trying out whether this will work. After `MainWindow(window)` is created, `app` will be accessible everywhere. – Thomas Lemberger Mar 11 '21 at 13:18
  • Actually, `Question` instances are created by the `show_question` method in `MainWindow`, which is called by the `__init__` method. So the flow goes: `main` creates `MainWindow` and its `__init__` calls the `show_question` method, which creates the `Question` instances which create the `Check` instances. What I just cannot understand is why one class ( `Check` ) is able to access the global, while others are not? Does not make much sense? Also, I cannot think of a way to resolve this. I guess I need any and every method to be able to reference the main window, or is there another way? – gbertonzzin Mar 11 '21 at 13:30
  • So is it a matter of 'timing' in the code's execution? Maybe if there's a 'landing' screen before any `Question` instances are created? That would give time for the `MainWindow` instance to be completelly executed? – gbertonzzin Mar 11 '21 at 13:35
  • 1
    Thanks for the clarification! I'm not an expert with tkinter, but it shouldn't be about timing; every step should be sequential unless you introduce multithreading during object creation. According to your description, the call to `Check#create_check()` shouldn't have access to `app`, as long as called during the creation of `MainWindow`. If this is what is happening, it is difficult to understand why without seeing the full code. – Thomas Lemberger Mar 11 '21 at 13:45
  • Regarding your question about the code structure: there certainly is some other solution, but it is difficult to give good advice without seeing more of the code. It may also make sense to ask a separate question for that. – Thomas Lemberger Mar 11 '21 at 13:45
  • Thanks a lot for the help! I managed to make it work somewhat by not calling the `show_question` right away, requiring a button to be pressed before any questions are displayed. What I take from this problem is that you are correct: `app` is in fact global, but only after the `MainWindow` instance has been created! So, __referencing it in a execution path that stems from it's `__init__` method will surely bring trouble__. Haven't still quite got it working the way I wanted, but i trust it will be a matter of time till I do. Again, much thank. Wish you have the happiest of days, my friend. – gbertonzzin Mar 11 '21 at 13:51
  • Glad I could help and best of luck! – Thomas Lemberger Mar 11 '21 at 13:53