简体   繁体   中英

Python threads and global variables

I have a class with some variables declared as globals in the first method. Another subsequent method starts a thread, and the problem is that python doesn't recognize those global variables after t.start(). Here is how the program works: 1) user can click "YES"-button on tkinter window 2) program then starts to upload data into database. This step takes a while (2-5 minutes) and to prevent UI from freezing during the upload, the program starts a thread that performs the sql stuff. At same time, the program clears widgets from the window and replaces them with new widgets (a progress bar and a text field). 3) after upload is completed, the program again refresh the tkinter window with new buttons and a scrollbox.

Here is the code snippet:

class Application(tk.Frame):

    def __init__(self, parent):
      #do some init here..

    def initUI(self):
        global text1, text2, button_no, button_yes, progress_bar #here are the globals
        frame1 = tk.Frame(self)
        frame1.pack()
        text1 = tk.Label(self, text="Do you want to upload a new log file?", background="white")
        button_yes = tk.Button(self, text="YES", command=self.removeButtonYes)
        button_no = tk.Button(self, text="NO", command=self.removeButtonNo)
        text1.pack()
        button_yes.pack()
        button_no.pack()
        self.pack(fill=tk.BOTH, expand=1)

    def removeButtonNo(self):
#do something here

    def removeButtonYes(self):
        text1.pack_forget() #first three lines clear those original three widgets
        button_no.pack_forget()
        button_yes.pack_forget()
#then add some text with the progress bar
        text2 = tk.Label(self, text="Transferring data. Please wait...", background="white")
        text2.pack()
        progress_bar = ttk.Progressbar(self, orient="horizontal", length=100, mode="indeterminate")
        progress_bar.pack()
        progress_bar.start(100)
        #initialize a thread to prevent the UI from freezing during sql inserts
        t = threading.Thread(target=self.writeLogtoDatabase)
        t.start()

    def writeLogtoDatabase(self):
        #open db connection, upload data and close db
        self.clearUI() #call a method to clear the progress bar and info text

    def clearUI(self):     
        text2.pack_forget()
        progress_bar.pack_forget()

It just throws following error message:

Exception in thread Thread-1:
Traceback (most recent call last):
  File "c:\python27\lib\threading.py", line 810, in __bootstrap_inner
    self.run()
  File "c:\python27\lib\threading.py", line 763, in run
    self.__target(*self.__args, **self.__kwargs)
  File "C:\Python27\test\testdb2.py", line 94, in writeLogtoDatabase
    self.clearUI()
  File "C:\Python27\test\testdb2.py", line 98, in clearUI
    text2.pack_forget()
NameError: global name 'text2' is not defined

Why? As you can see, I can call those variables outside the method where they are declared. Has this something to do with threading - a thing, that I am not very familiar with?

Unless I don't forget those text2 and progress bar widgets, they will show up in the last window which is not desired functionality.

You should add global text2 on the removeButtonYes method (and also for progress_bar , otherwise you'll have the same problem again). It's completely useless to add a global text2 statement in a function that doesn't define that variable.

Also I don't see the advantage of using a global variable in this case, except that it's really easy to create bugs. Why don't you simply use an instance attribute self.text2 and self.progress_bar ?

the global statement does not create a global. It simply means that assignments to the given names are to assigned in the global context, not the local context. It also means a name access will get a global variable, rather than a local variable. if you only access a global variable then python is able to figure out the variable is a global rather than a local without the need for a global statement.

eg.

def adder(to_add):
    global total
    total = total + to_add
    return total

try:
    # total does not exist at this point
    adder(1)
except NameError as e:
    print(e)

total = 10
print(adder(4))

Your problem is that you declared a text2 to be global, but then did not assign to text2 , so the name text2 was never created in the global context

When you later methods assign to text2 they create the name in the method's local context (since text2 is not declared to be global there). One the method ends, the Label object is dereferenced and garbage collected.

You really have no need to be using globals though. You have an instance of Application (called self in your methods) which you can assign your objects to. It's really easy to access these objects outside of your Application instance if you need to

eg.

app = Application() # assigns some object to text2 on self
app.text2.pack()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM