重定向sys.stdout到Tkinter.Text小部件时段错误
我正在用Python和Tkinter开发一个图形界面的应用程序,这个程序是基于现有的Python bdb模块的。在这个应用中,我想把控制台的所有输出(包括正常输出和错误输出)都静音,然后把它们重定向到我的图形界面里。为此,我写了一个特别的Tkinter.Text对象(代码在帖子最后)。
基本的想法是,当有东西写入sys.stdout时,它会以黑色的形式显示在“Text”中。如果有东西写入sys.stderr,它会以红色的形式显示在“Text”中。每当有新内容写入时,Text会自动向下滚动,以便查看最新的内容。
目前我使用的是Python 2.6.1。在Mac OS X 10.5上,这个功能运行得很好,我没有遇到任何问题。然而,在RedHat Enterprise Linux 5上,我在运行脚本时经常会遇到“段错误”。这个段错误并不总是在同一个地方出现,但几乎总是会发生。如果我把代码中的sys.stdout=
和sys.stderr=
那两行注释掉,段错误似乎就消失了。
我相信还有其他解决办法,我可能需要尝试,但有没有人能指出我这里做错了什么,导致这些段错误的发生?这让我很头疼。谢谢!
附言 - 我知道把sys.stderr重定向到图形界面可能不是个好主意,但即使我只重定向sys.stdout而不重定向sys.stderr,仍然会出现段错误。我也意识到目前我让Text对象无限增长。
class ConsoleText(tk.Text):
'''A Tkinter Text widget that provides a scrolling display of console
stderr and stdout.'''
class IORedirector(object):
'''A general class for redirecting I/O to this Text widget.'''
def __init__(self,text_area):
self.text_area = text_area
class StdoutRedirector(IORedirector):
'''A class for redirecting stdout to this Text widget.'''
def write(self,str):
self.text_area.write(str,False)
class StderrRedirector(IORedirector):
'''A class for redirecting stderr to this Text widget.'''
def write(self,str):
self.text_area.write(str,True)
def __init__(self, master=None, cnf={}, **kw):
'''See the __init__ for Tkinter.Text for most of this stuff.'''
tk.Text.__init__(self, master, cnf, **kw)
self.started = False
self.write_lock = threading.Lock()
self.tag_configure('STDOUT',background='white',foreground='black')
self.tag_configure('STDERR',background='white',foreground='red')
self.config(state=tk.DISABLED)
def start(self):
if self.started:
return
self.started = True
self.original_stdout = sys.stdout
self.original_stderr = sys.stderr
stdout_redirector = ConsoleText.StdoutRedirector(self)
stderr_redirector = ConsoleText.StderrRedirector(self)
sys.stdout = stdout_redirector
sys.stderr = stderr_redirector
def stop(self):
if not self.started:
return
self.started = False
sys.stdout = self.original_stdout
sys.stderr = self.original_stderr
def write(self,val,is_stderr=False):
#Fun Fact: The way Tkinter Text objects work is that if they're disabled,
#you can't write into them AT ALL (via the GUI or programatically). Since we want them
#disabled for the user, we have to set them to NORMAL (a.k.a. ENABLED), write to them,
#then set their state back to DISABLED.
self.write_lock.acquire()
self.config(state=tk.NORMAL)
self.insert('end',val,'STDERR' if is_stderr else 'STDOUT')
self.see('end')
self.config(state=tk.DISABLED)
self.write_lock.release()
2 个回答
我假设这是一个更大、更复杂的程序的一部分。
与其使用锁,不如让你的代码写入一个线程安全的队列对象。然后,在你的主线程中,你可以定期检查这个队列,并将内容写入文本小部件。你可以通过事件循环来进行这个检查(而不是自己写一个循环),方法是运行一个检查任务,然后让它在几毫秒后自动重新安排自己运行(几百毫秒就足够了)。
好的,我终于找到了问题所在。我在最初开发代码的Mac OS X 10.5.8上从来没有遇到过这个问题。这个分段错误似乎只发生在RedHat Enterprise Linux 5上。
结果发现,这段代码是罪魁祸首:
def write(self,val,is_stderr=False):
#Fun Fact: The way Tkinter Text objects work is that if they're disabled,
#you can't write into them AT ALL (via the GUI or programatically). Since we want them
#disabled for the user, we have to set them to NORMAL (a.k.a. ENABLED), write to them,
#then set their state back to DISABLED.
self.write_lock.acquire()
self.config(state=tk.NORMAL)
self.insert('end',val,'STDERR' if is_stderr else 'STDOUT')
self.see('end')
self.config(state=tk.DISABLED)
self.write_lock.release()
我希望能解释一下为什么会出现分段错误,但我发现不断启用和禁用Text对象就是问题所在。如果我把上面的代码改成这样:
def write(self,val,is_stderr=False):
self.write_lock.acquire()
self.insert('end',val,'STDERR' if is_stderr else 'STDOUT')
self.see('end')
self.write_lock.release()
当我去掉self.config(state=...)
的调用时,分段错误就消失了。调用self.config(state=...)
的初衷是为了让用户无法编辑Text字段。不过,当Text字段处于tk.DISABLED
状态时,调用self.insert(...)
也无法正常工作。
我想出的解决办法是保持Text字段可用,但让它忽略所有键盘输入(这样用户尝试使用键盘时就会有只读的感觉)。实现这个的最简单方法是把__init__
方法改成这样(把状态改为tk.NORMAL
,并更改<Key>
事件的绑定):
def __init__(self, master=None, cnf={}, **kw):
'''See the __init__ for Tkinter.Text for most of this stuff.'''
tk.Text.__init__(self, master, cnf, **kw)
self.started = False
self.write_lock = threading.Lock()
self.tag_configure('STDOUT',background='white',foreground='black')
self.tag_configure('STDERR',background='white',foreground='red')
self.config(state=tk.NORMAL)
self.bind('<Key>',lambda e: 'break') #ignore all key presses
希望这能帮助到遇到同样问题的人。