views:

46

answers:

3

This is probably very basic, but it's giving me a headache, and I'm not sure what method to even approach it with, making the googling tough.

If I have a class in a module that I'm importing with various prints throughout, how can I read the prints as they come so that I may output them to a PyQT text label?

class Worker(QtCore.QThread, object):
    class statusWrapper(object):
        def __init__(self, outwidget):
            self.widget = outwidget

        def write(self, s):
            self.widget.setText(s)

    def __init__(self, widget):
        QtCore.QThread.__init__(self)
        sys.stdout = statusWrapper(widget)

    def run(self):
        self.runModule() #This is the module with the prints within.

Something mysterious gets passed to the statusWrapper.write when runModule gets executed, but it's blank. What am I doing wrong?

Thanks.

A: 

The easiest (still wrong - the right way is changing the function) way is redirecting stdout to the label (print writes to sys.stdout). See http://stackoverflow.com/questions/3548937/wxwidgets-and-pyqt/3549337#3549337 for a trivial example with a QPlainTextEdit, should be easy to adjust for a QLabel.

delnan
Thanks. How should I change the function?
Cryptite
Huh? Well, return the result instead of printing them... that's how most *functions* should work anyway.
delnan
I got close but there's some strange looping and errors that eventually exceed recursion depth when I try stuff out. Updated the OP with the current code.
Cryptite
+1  A: 

Instead of writing your own statusWrapper class, you could use a StringIO object as stdout. Something like:

def __init__(self, widget):
    QtCore.QThread.__init__(self)

def run(self):
    real_stdout = sys.stdout
    sys.stdout = StringIO.StringIO()
    self.runModule()
    label_text = sys.stdout.getvalue()
    sys.stdout = real_stdout

Restoring the original value of stdout is important for your sanity. Also note that this will not do what you expect in a multithreaded environment. Also note that delnan is certainly correct, and replacing stdout is an incredibly hackish way of doing this.

If you're wanting something which will update the label each time the module prints output (sort of a poor man's status indicator), there are better ways to do that too - you could replace the prints with calls to a callback function, which you set in the module when you import it, or something like that.

p-static
Thanks, that works. This is hackish indeed... :P
Cryptite
+1  A: 

Something mysterious gets passed to the statusWrapper.write when runModule gets executed, but it's blank. What am I doing wrong?

It's nothing mysterious: write just receives each string that was written to sys.stdout (that is, your wrapper, in this case).

The bug is probably that the wrapper calls setText only, replacing the widget's text on each write, instead of appending to it. You'll need to at least do something like:

    def write(self, s):
        self.widget.setText(self.widget.text() + s)

(or whatever the more efficient way is of appending text to a QT widget).

Note:

A much better way to redirect sys.stdout is to use a context manager. PEP 343 defines the following example:

from contextlib import contextmanager

@contextmanager
def stdout_redirected(new_stdout):
    save_stdout = sys.stdout
    sys.stdout = new_stdout
    try:
        yield None
    finally:
        sys.stdout = save_stdout

You would use it like:

class Worker(QtCore.QThread, object):

    def run(self):
        with stdout_redirected(StatusWrapper(widget)):
            self.runModule()

Besides being more readable, this context manager makes sure to restore sys.stdout if runModule raises an exception (which is important for your sanity :-).

Piet Delport