2

I have my code successfully printing sys.stdout to my GUI Tkinter text widget. I am getting some strange behavior from my program and I can't figure out why; I need help troubleshooting.

The biggest problem is that sys.stderr is not printing to my GUI or to my IDE results. It may be the result of how I got sys.stdout to go to my GUI but I am not knowledgeable enough to diagnose whether that's the case. I followed this code: Python multiprocessing redirect stdout of a child process to a Tkinter Text. It may also have something to do with why my code is apparently running twice.

I have my main program running the GUI and use multiprocessing to run a separate script from an imported module I made. One of the first things this script does is check for errors in the inputs. If there's an error, I have it give a custom exception to end the process without closing Python. The exception uses tkMessageBox to generate a window to alert the user that the program needs attention. It used to print traceback to the GUI but no longer does so now that I am redirecting sys.stdout.

Anyway, when I run the script and it gets to that exception, it brings up two windows. One looks like a Windows-generated OS message box with the message inside that I programmed. The other is an empty white window with the title "Tk" that disappears when I hit "OK" on the other message box. Sometimes Python crashes after hitting "OK". The GUI doesn't crash so it must be the subprocess that crashes. Edit: The second window problem is solved and my code below has been updated to reflect that. However, the subprocess is still crashing when I hit "OK" on the message box and still loading the module twice (apparently).

I also have a print statement at the beginning of my imported module. The module is imported before my GUI is created and shows up in my IDE. When I initiate the function in my module using multiprocessing, the print statement shows in the IDE again. That statement isn't part of the function I am running.

I have no background in computer science so it could be something totally obvious that I'm not seeing.

Please take a look and see what could be going on. I've simplified my code and it should run for you. I'm running Windows 8.1 and Python 2.7.9.

GUI/main loop:

#### _______________IMPORT MODULES_________________###
import Tkinter
from multiprocessing.queues import Queue
from multiprocessing import Process
import sys, traceback
from threading import Thread
import qBMPchugger

###_____Create Text Catcher ______###
def text_catcher(text_widget, queue):
    while True:
        text_widget.insert("end", queue.get())
        app.update_idletasks()

class StdoutQueue(Queue):
    def __init__(self, *args, **kwargs):
        Queue.__init__(self, *args, **kwargs)

    def write(self, msg):
        self.put(msg)

    def flush(self):
        sys.__stdout__.flush()

def ErrorGrab(type, value, traceback):
    exception_string = "".join(traceback.format_exception(type, value, traceback))
    # do whatever you want from here
    print exception_string
    print "testing my excepthook"

###____________Widget__________________###
class InputBox(Tkinter.Tk):
    def __init__(self,parent):
        Tkinter.Tk.__init__(self, parent)
        self.parent = parent
        self.initialize()

    def initialize(self):
        # Build Frame
        self.OK = Tkinter.Frame(self, padx=3, pady=3)
        self.OK.grid(column=0, row=2, columnspan=3, sticky="EW")
        self.printFrame = Tkinter.LabelFrame(self, borderwidth=3, relief="ridge", padx=3, pady=3, text="Results")
        self.printFrame.grid(column=0, row=3, columnspan=4, sticky="EW")
        # Build "run" button
        self.OKbutton = Tkinter.Button(self.OK, text=u"OK", command=self.OKgo, anchor="e")
        self.OKbutton.pack(side="right")
        # Build output viewer
        self.view = Tkinter.Text(self.printFrame)
        self.view.grid(column=0, row=0, columnspan=3, sticky="EW")
        self.scroll = Tkinter.Scrollbar(self.printFrame, orient=Tkinter.VERTICAL)
        self.scroll.config(command=self.view.yview)
        self.view.config(yscrollcommand=self.scroll.set)
        self.scroll.grid(column=4, row=0, sticky="SN")

    def OKgo(self):
        sys.stdout = q
        sys.excepthook = ErrorGrab

        monitor = Thread(target=text_catcher, args=(self.view, q))
        monitor.daemon = True
        monitor.start()

        self.view.delete(1.0, "end")
        self.update_idletasks()

        print("Loading user-specified inputs...")
        WTin = "D:/Python/Inputs/wtdepth1.aux"

        print("LoadingModule")
        self.update_idletasks()
        import qBMPchugger
        self.update_idletasks()

        # Starts the child process, qBMPchugger.BMPcode
        inarg = (q, WTin)
        p = Process(target=qBMPchugger.BMPcode, args=inarg)
        p.start()
        p.join()

        print("ended")

if __name__ == "__main__":
    app = InputBox(None)
    app.title("File Inputs and Program Settings")

    # Start the text monitor
    q = StdoutQueue()
    monitor = Thread(target=text_catcher, args=(app.view, q))
    monitor.daemon = True
    monitor.start()

    app.mainloop()

Module "qBMPchugger":

#### _______________INITIALIZE_________________###
print("Creating the program environment and importing modules...")
import os
import sys
import tkMessageBox
import Tkinter

# Create user-defined exceptions
class BadInput(Exception):
    pass

def BMPcode(q, WTin):  
    ### _________________VERIFY INPUTS________________###
    boxRoot = Tkinter.Tk()
    boxRoot.withdraw()

    sys.stdout = q
    sys.stderr = q

    print("Checking out the Spatial Analyst extension from GIS...")
    # Check out extension and overwrite outputs

    print("Checking validity of specified inputs...")
    # Check that the provided file paths are valid
    inputs = [WTin]
    for i in inputs:
        if os.path.exists(i):
            pass
        else:
            message = "\nInvalid file path: {}\nCorrect the path name and try again."
            tkMessageBox.showerror("Invalid Path", message.format(i))
            raise BadInput(message.format(i))

    print("Success!")

Edit: I removed a section from my module that used arcpy; it shouldn't be the source of the error.

UPDATE 1: I tried moving my import qBMPchugger statement from the beginning of my GUI code to my function OKgo. The result was that print("Creating the program environment and importing modules...") prints once in the GUI when it first loads and then prints again in the IDE when it starts the second process. If I run OKgo again without closing the GUI, it only prints in the IDE.

I then closed the GUI and reran the program to check where the print statement is printed first and I got an error; it printed in the IDE. I don't think I changed the code between runs so that's interesting. I'll be investigating this traceback now I guess. I updated my code to reflect the changed location of import qBMPchugger.

Exception in thread Thread-1:
Traceback (most recent call last):
  File "D:\Python\Python27\lib\threading.py", line 810, in __bootstrap_inner
self.run()
  File "D:\Python\Python27\lib\threading.py", line 763, in run
self.__target(*self.__args, **self.__kwargs)
  File "D:/Python/Pycharm/Projects/BMP_Tool/GUI-thread.py", line 13, in text_catcher
app.update_idletasks()
  File "D:\Python\Python27\lib\lib-tk\Tkinter.py", line 1020, in update_idletasks
    self.tk.call('update', 'idletasks')
TclError: bad option ".242660736.242702216,relief": must be idletasks

UPDATE 2: I have been messing around with sys.stdout and sys.stderr. I deleted any redirection of stderr and stdout in my main program and did sys.stderr = q in the child process. I received an exception that looks like half of the exception I programmed; it is missing the message and the name of the exception. I ran the function again without exiting the GUI and it gave the same exception but it said "Process 2", and again with "Process 3". Closing the GUI and running it again gave me the full exception, but running it again gave me the shortened version.

Process Process-1:
Traceback (most recent call last):
  File "D:\Python\Python27\lib\multiprocessing\process.py", line 258, in _bootstrap
    self.run()
  File "D:\Python\Python27\lib\multiprocessing\process.py", line 114, in run
    self._target(*self._args, **self._kwargs)
  File "D:\Python\Pycharm\Projects\BMP_Tool\qBMPchugger.py", line 44, in BMPcode

UPDATE 3: It appears as if the child can only print either stdout or stderr in the GUI, not both, and it has a preference for stdout.

Community
  • 1
  • 1
Rachael
  • 39
  • 3
  • 2
    For a process to use Tkinter, it must have a root window. Thus, if you have two processes that use Tkinter, you'll get two root windows. – Bryan Oakley Aug 07 '15 at 18:28
  • Oooh. That explains the second window. Is there a way to make the parent root the child root or otherwise get rid of that second box? That's not what's causing the module print statement to load twice, is it? – Rachael Aug 07 '15 at 18:34
  • Found a solution to get the second box to go away: http://stackoverflow.com/questions/17280637/tkinter-messagebox-without-window once I knew to look at problems involving a root. Thanks. Unfortunately it still is loading the module twice and crashing after hitting "ok". But that minor annoyance is gone! – Rachael Aug 07 '15 at 18:45

0 Answers0