【问题标题】:Python threads and global variablesPython 线程和全局变量
【发布时间】:2014-11-13 08:29:20
【问题描述】:

我有一个类,其中一些变量在第一种方法中声明为全局变量。另一种后续方法启动一个线程,问题是python无法识别t.start()之后的那些全局变量。以下是该程序的工作原理: 1) 用户可以单击 tkinter 窗口上的“是”按钮 2)然后程序开始将数据上传到数据库中。此步骤需要一段时间(2-5 分钟),为了防止 UI 在上传期间冻结,程序启动了一个执行 sql 内容的线程。同时,程序从窗口中清除小部件并用新的小部件(进度条和文本字段)替换它们。 3)上传完成后,程序再次用新按钮和滚动框刷新tkinter窗口。

这里是sn-p的代码:

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()

它只是抛出以下错误消息:

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

为什么?如您所见,我可以在声明它们的方法之外调用这些变量。这与线程有关 - 我不太熟悉的东西吗?

除非我没有忘记那些 text2 和进度条小部件,否则它们将显示在最后一个窗口中,这是不想要的功能。

【问题讨论】:

  • 在线程中使用全局变量可能非常危险,您需要确保在任何更改之前锁定变量并在之后释放它(除非它是只读的),您应该考虑使用Queue ,也可以参考文档Queue

标签: python tkinter global


【解决方案1】:

您应该在removeButtonYes 方法上添加global text2(以及progress_bar,否则您将再次遇到同样的问题)。在未定义该变量的函数中添加global text2 语句是完全没用的。

我也看不出在这种情况下使用全局变量的好处,除了它真的很容易产生错误。为什么不简单地使用实例属性self.text2self.progress_bar

【讨论】:

    【解决方案2】:

    global 语句不会创建全局变量。它只是意味着对给定名称的分配是在全局上下文中分配的,而不是在本地上下文中分配的。这也意味着名称访问将获得一个全局变量,而不是一个局部变量。如果您只访问一个全局变量,那么 python 能够找出该变量是全局变量而不是局部变量,而无需 global 语句。

    例如。

    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))
    

    您的问题是您将text2 声明为全局,但随后没有分配给text2,因此从未在全局上下文中创建名称text2

    当您稍后将方法分配给 text2 时,它们会在方法的本地上下文中创建名称(因为 text2 未在此处声明为全局)。一种方法结束,Label 对象被取消引用并被垃圾回收。

    不过,您确实没有必要使用全局变量。您有一个Application 的实例(在您的方法中称为self),您可以将您的对象分配给它。如果需要,在 Application 实例之外访问这些对象真的很容易

    例如。

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

    【讨论】:

    • 感谢您澄清这一全球性问题。我通过将全局 text2、progress_bar 添加到方法 removeButtonYes() 中进行了测试。这在执行时崩溃了 python。您能否解释一下在这种情况下如何调用这些实例属性(self.text2 和 self.progress_bar)?
    • python是怎么崩溃的?有什么例外?是NameError 还是AttributeError?不过,我认为,iIn initUI 您应该创建所需的所有 UI 元素并将它们分配给 self 例如。 self.text2 = Label(...)。如果您遇到不同的异常,那么您应该就该问题提出另一个问题。
    • 带有进度条的 Tkinter 窗口刚刚冻结,然后 Windows7 说 python.exe 没有响应。实际上,当我在 initUI() 中声明这些 UI 元素并尝试在 clearUI() 中访问它们时,发生了完全相同的现象。我还尝试在clearUI() 中调用self.destroy(),但它会清除在步骤3 中添加的小部件(以单独的方法)。
    • 这是一个关于如何使用 tkinter 的单独问题。请把它作为一个新问题发布。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多