简体   繁体   中英

Threading with pubsub throwing AssertionError: 'callableObj is not callable' in wxPython

I have a python program that on a push of a button I want to do a certain task in a separate thread to stop the task from making the GUI unresponsive due to using time.sleep() in the task in hand. I have an issue with the thread where when the wx.CallAfter() is used with pub.sendMessage() I get an exception. I need the pub sub to send information between the threads.

Below is an example of the issue I am seeing, the code isn't doing what I really want but it shows the error in the same way. This code creates a button that when pressed creates a thread that creates a tuple and then sends the string part of the tuple to the status bar on the frame:

#!/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()

When I run this code and press the button the status bar updates but I also see a Traceback as shown below:

TestGUI Showing Status Bar Update

Traceback:

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

I am at a complete loss as to why the status bar updates as expected but I get an Exception raised? Is there something obvious I have missed here, I haven't done threading before and this is my first GUI attempt with Python and wxPython. I am jumping in at the deep end. I have seen solutions where it was a namespace issue but I can't see a namespace conflict here.

EDIT: Grammar fixes.

Never worked with wx , but here's how callAfter signature looks like: [wxPython]: wx.CallAfter ( callableObj , *args , **kw ) .

You have to pass the function/method (callable) and its arguments (positional/keyword) separately , and not actually call it (just like in [Python]: class threading. Thread ( group = None , target = None , name = None , args = () , kwargs = {} ) ).

I assume that you want callAfter to call sendMessage like you specified in the snippet:

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

Then, your line:

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

should be:

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

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

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

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