使用pubsub抛出断言AssertionError:' callableObj不可调用'在wxPython中

时间:2017-10-20 15:20:50

标签: python multithreading python-2.7 wxpython

我有一个python程序,只需按一下按钮,我就可以在一个单独的线程中执行某项任务,以阻止任务因为在手头的任务中使用time.sleep()而使GUI无响应。我有一个线程的问题,当wx.CallAfter()与pub.sendMessage()一起使用时,我得到一个异常。我需要pub子在线程之间发送信息。

下面是我看到的问题的一个例子,代码并没有做我真正想要的事情,但它以同样的方式显示错误。此代码创建一个按钮,按下该按钮创建一个创建元组的线程,然后将元组的字符串部分发送到框架上的状态栏:

#!/usr/bin/env python2.7

import wx

from wx.lib.pubsub import pub
from threading import Thread

#====================================
# Main Application Frame Class
#====================================
class MainFrame(wx.Frame):
    """The main frame class for the application."""
    # MainFrame Constructor Method
    def __init__(self, *args, **kwargs):
        """Initialise the main application frame and bind events to event handlers."""
        wx.Frame.__init__(self, style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER, *args, **kwargs)

        self.appStatusBar = self.CreateStatusBar()
        self.panel = MainPanel(self)

        # Set up the file menu
        filemenu = wx.Menu()
        menuAbout = filemenu.Append(wx.ID_ABOUT, "&About", " Testing Publisher with Threading")
        menuExit = filemenu.Append(wx.ID_EXIT, "E&xit", " Terminate Program")

        # Set up a menu bar for placing the file menu
        menuBar = wx.MenuBar()
        menuBar.Append(filemenu, "&File")
        self.SetMenuBar(menuBar)

        # Set the events that will trigger from the users interaction
        self.Bind(wx.EVT_MENU, self.onAbout, menuAbout)
        self.Bind(wx.EVT_MENU, self.onExit, menuExit)

        # Use some sizers to see layout options
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.panel, proportion=1, flag=wx.EXPAND)

        # Layout the sizers
        self.SetSizer(self.sizer)
        self.SetAutoLayout(1)
        self.sizer.Fit(self)

        # Set up listeners for the status bar and error dialog so that they can be implemented from other classes
        pub.subscribe(self.changeStatusBar, "changeStatus")
        pub.subscribe(self.errorMsgDisp, "errorDisplay")

        self.Centre()
        self.Show(True)
    # End of MainFrame Constructor Method

    # onAbout Method Functionality
    def onAbout(self, e):
        """Open Program About Dialog.

        :param e: The event that triggered the method
        """
        dlg = wx.MessageDialog(self, "Testing Publisher with Threading", "About Program", wx.OK)
        dlg.ShowModal()
        dlg.Destroy()
    # End of onAbout() Method

    # onExit Method Functionality
    def onExit(self, e):
        """Close the GUI down.

        :param e: The event that triggered the method
        """
        self.Close()
    # End of onExit() Method

    # Update the Status Bar Message Method
    def changeStatusBar(self, msg):
        """Change the message displayed on the status bar.

        :param msg: Message to be displayed in the status bar
        """
        self.appStatusBar.SetStatusText(msg)
        self.appStatusBar.Refresh()
    # End of changeStatusBar() Method

    # Display Error Messages Method
    def errorMsgDisp(self, msg):
        """Display the error message sent to the function.

        :param msg: The string with the error message to be displayed
        """
        dlg = wx.MessageDialog(None, msg, "Error", wx.OK | wx.ICON_ERROR)
        dlg.ShowModal()
        dlg.Destroy()
    # End of errorMsgDisp() Method

# End of MainFrame class


#====================================
# Main Panel Class
#====================================
class MainPanel(wx.Panel):
    """The main panel class for the application.

    The main panel for holding all the widgets for the tool.
    """
    # MainPanel Constructor Method
    def __init__(self, parent, *args, **kwargs):
        """Set up the main panel that all the widgets are tied to.

        Defines all the widgets and events that are to occur when the widgets
        are used.

        :param parent: The parent frame/panel that the MainPanel belongs to
        """
        wx.Panel.__init__(self, parent, *args, **kwargs)

        self.mainVBox = wx.BoxSizer(wx.VERTICAL)

        self.testingButton = wx.Button(self, label="Testing Button")

        self.Bind(wx.EVT_BUTTON, self.onTestButton, self.testingButton)

        # Add the COMs Panel to the main panel Vertical box sizer
        self.mainVBox.Add(self.testingButton, proportion=1, flag=wx.EXPAND)

        self.SetSizer(self.mainVBox)

    # Event for doing something with the button
    def onTestButton(self, e):
        testBtn = e.GetEventObject()
        testBtn.Disable()

        testingThread = WorkerThread()
        testingThread.start()

# End of MainPanel class

#====================================
# Activity Thread Class
#====================================
class WorkerThread(Thread):
    """Worker thread class for doing all time consuming tasks."""
    # WorkerThread Constructor Method
    def __init__(self):
        """Initialises the worker thread ready to run tasks."""
        Thread.__init__(self)
    # End of WorkerThread Constructor Method

    # Worker Run Method
    def run(self):
        """When the thread is started the tasks in this method are executed."""
        self.testButton()       
    # End of run() Method

    # Test the button
    def testButton(self):
        """Create tuple and publish the string to the status bar"""
        testResults = (0, "Status Bar Updated")
        wx.CallAfter(pub.sendMessage("changeStatus", msg=testResults[1]))
    # End of testButton() Method

# End of WorkerThread Class


#====================================
# Main Code that Runs the GUI
#====================================
if __name__ == '__main__':
    app = wx.App(False)
    frame = MainFrame(None, title="Threading Test")
    app.MainLoop()

当我运行此代码并按下按钮时,状态栏会更新,但我也会看到一个Traceback,如下所示:

TestGUI Showing Status Bar Update

回溯:

Exception in thread Thread-1:
Traceback (most recent call last):
  File "C:\Python27\lib\threading.py", line 801, in __bootstrap_inner
    self.run()
  File "C:\Users\Mhaines\Documents\threading_pubsub_test.py", line 150, in run
    self.testButton()
  File "C:\Users\Mhaines\Documents\threading_pubsub_test.py", line 157, in testButton
    wx.CallAfter(pub.sendMessage("changeStatus", msg=testResults[1]))
  File "C:\Python27\lib\site-packages\wx-3.0-msw\wx\_core.py", line 16759, in CallAfter
    assert callable(callableObj), "callableObj is not callable"
AssertionError: callableObj is not callable

我完全不知道为什么状态栏会按预期更新,但我收到异常?有没有明显我错过的东西,我以前没有做过线程,这是我第一次使用Python和wxPython进行GUI尝试。我在深处跳进去。我已经看到了解决方案,这是一个命名空间问题,但我在这里看不到命名空间冲突。

编辑:语法修复。

2 个答案:

答案 0 :(得分:0)

从未使用 wx ,但这里是callAfter签名的样子: [wxPython]: wx.CallAfter(callableObj, *args, **kw)

您必须传递函数/方法(可调用)及其参数(位置/关键字)单独实际上不调用(就像在{{3}中一样}})。

我认为您希望callAfter像您在代码段中指定的一样调用sendMessage

pub.sendMessage("changeStatus", msg=testResults[1])

然后,你的行:

 wx.CallAfter(pub.sendMessage("changeStatus", msg=testResults[1]))

应该是:

 wx.CallAfter(pub.sendMessage, args=("changeStatus",), kw={"msg": testResults[1]})

答案 1 :(得分:0)

wx.CallAfter接受一个函数及其参数

    wx.CallAfter(pub.sendMessage,"changeStatus", msg=testResults[1])