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.