pygtk调试输出到控制台

1 投票
1 回答
1917 浏览
提问于 2025-04-17 04:05

我遇到了一个问题。我正在创建一个编辑器,需要把控制台的输出(包括错误信息和正常信息)显示到一个文本视图里。问题是,当我启动控制台时,它会等待控制台退出,但我希望它能实时捕捉到任何输出并显示在文本视图中。我想这可能需要用到不同的线程,但这样的话,是否就无法从另一个线程捕捉到信息呢?我这样做是为了确保即使编辑器不是从终端启动的也能正常工作。如果你在想这段代码是用来做什么的,它将作为一个模块使用。以下是目前的代码:

import sys
import gtk
import pygtk
pygtk.require('2.0')

class Console:
    def __init__(self):
        tv = gtk.TextView()
        tv.set_editable(False)
        tv.set_wrap_mode(gtk.WRAP_WORD)
        self.buffer = tv.get_buffer()

        table = gtk.Table(3, 6, gtk.FALSE)
        table.attach(tv, 0, 6, 0, 1)

        #### Main window
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.connect('destroy_event', lambda w, e: gtk.mainquit())
        self.window.connect('delete_event', lambda w, e: gtk.mainquit())
        self.window.set_border_width(10)
        self.window.add(table)
        self.window.set_title('Search')
        self.window.set_default_size(300, 300)
        self.window.show_all()

    def main(self):
        gtk.main()

c = Console()

class ConsoleOutput:
    def __init__(self, source):
        self.source=source
        self.buf = []

    def write(self, data):
        self.buf.append(data)
        if data.endswith('\n'):
          c.buffer.insert(c.buffer.get_end_iter(), ''.join(self.buf))
          self.buf = []

    def __del__(self):
        if self.buf != []:
          c.buffer.insert(c.buffer.get_end_iter(), ''.join(self.buf))

谢谢。

1 个回答

2

因为你想要捕捉 sys.stdoutsys.stderr,而根据文档,这两个是全局的,所以无论输出是在什么线程中进行的,你都应该能够捕捉到它们。

另一方面,你的代码离成功也不远了。只是缺少了对事件循环的调用,这就是它为什么会卡住的原因。我在代码中添加了对 c.main() 的调用。

我还添加了一个按钮,点击后会打印“hello”,并且我把默认的 sys.stdout 替换成了一个 ConsoleOutput 的实例,这样“hello”就会出现在文本视图中。

import sys
import gtk
import pygtk
pygtk.require('2.0')
import gobject

import threading

class MyThread(threading.Thread):
    def __init__(self, name):
        threading.Thread.__init__(self)
        self.name = name
    def run(self):
        for i in range(10):
            print "Hello %d from thread %s" % (i, self.name)

class Console:
    def __init__(self):
        tv = gtk.TextView()
        tv.set_editable(False)
        tv.set_wrap_mode(gtk.WRAP_WORD)
        self.buffer = tv.get_buffer()

        button = gtk.Button("Update")
        button.connect("clicked", self.update, None)

        table = gtk.Table(3, 6, gtk.FALSE)
        table.attach(tv, 0, 6, 0, 1)
        table.attach(button, 0, 6, 1, 2)

        #### Main window
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.connect('destroy_event', lambda w, e: gtk.mainquit())
        self.window.connect('delete_event', lambda w, e: gtk.mainquit())
        self.window.set_border_width(10)
        self.window.add(table)
        self.window.set_title('Search')
        self.window.set_default_size(300, 300)
        self.window.show_all()

    def update(self, widget, data=None):
        print "hello"
        MyThread("A").start()
        MyThread("B").start()

    def main(self):
        gtk.main()

c = Console()

class ConsoleOutput:
    def __init__(self, source):
        self.source=source
        self.buf = []

    def update_buffer(self):
        c.buffer.insert(c.buffer.get_end_iter(), ''.join(self.buf))
        self.buf = []

    def write(self, data):
        self.buf.append(data)
        if data.endswith('\n'):
            gobject.idle_add(self.update_buffer)

    def __del__(self):
        if self.buf != []:
            gobject.idle_add(self.update_buffer)

sys.stdout = ConsoleOutput(None)
c.main()

补充:我更新了我的回答,加入了一个关于线程的例子。按下按钮时会创建两个线程。我使用了 Python 的 threading 模块。我觉得 pygtk 也有自己的线程功能,只是当我在谷歌搜索时,Python 的模块先出现了。

这里要强调的重要点在于 ConsoleOutput 类。注意我把更新控制台缓冲区的代码封装在一个叫 self.update_buffer 的方法里,这个方法是通过 gobject.idle_add 间接调用的。这个函数的作用是在 gtk 事件循环中调用 self.update_buffer。必须这样做,因为所有更新 GUI 的调用都必须在事件循环中进行,否则 Gtk 无法同步访问它的数据结构,可能会出现奇怪的行为或崩溃。

可能会有一些缓冲问题,导致输出没有立即出现在文本视图中。

撰写回答