pygtk调试输出到控制台
我遇到了一个问题。我正在创建一个编辑器,需要把控制台的输出(包括错误信息和正常信息)显示到一个文本视图里。问题是,当我启动控制台时,它会等待控制台退出,但我希望它能实时捕捉到任何输出并显示在文本视图中。我想这可能需要用到不同的线程,但这样的话,是否就无法从另一个线程捕捉到信息呢?我这样做是为了确保即使编辑器不是从终端启动的也能正常工作。如果你在想这段代码是用来做什么的,它将作为一个模块使用。以下是目前的代码:
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 个回答
因为你想要捕捉 sys.stdout
和 sys.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 无法同步访问它的数据结构,可能会出现奇怪的行为或崩溃。
可能会有一些缓冲问题,导致输出没有立即出现在文本视图中。