我正在使用Python / Tkinter构建基于GUI的应用程序,该应用程序构建在现有的Python bdb模块之上。在这个应用程序中,我想从控制台静默所有stdout / stderr并将其重定向到我的GUI。为了达到这个目的,我编写了一个专门的Tkinter.Text对象(帖子末尾的代码)。
基本思想是,当某些内容写入sys.stdout时,它会在“Text”中显示为黑色的一行。如果向sys.stderr写入了某些内容,它会在“Text”中显示为红色的一行。只要写入了某些内容,文本就会向下滚动以查看最近的行。
我目前正在使用Python 2.6.1。在Mac OS X 10.5上,这看起来效果很好。我没有遇到任何问题。但是,在RedHat Enterprise Linux 5上,我非常可靠地在脚本运行期间遇到分段错误。分段错误并不总是出现在同一个地方,但几乎总是会发生。如果我在代码中注释掉sys.stdout=
和sys.stderr=
行,则细分错误似乎就会消失。
我确信还有其他方法可以解决这个问题,但任何人都可以看到我在这里发生的明显错误,这可能导致这些细分错误吗?这让我疯了。谢谢!
PS - 我意识到将sys.stderr重定向到GUI可能不是一个好主意,但即使我只重定向sys.stdout而不是sys.stderr,我仍然会遇到分段错误。我也意识到我现在允许文本无限增长。
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()
答案 0 :(得分:4)
好吧,所以我设法追查问题。我无法在最初开发代码的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=...)
调用的重点是使用户无法编辑文本字段。当文本字段处于tk.DISABLED
状态时,对self.insert(...)
的调用也不起作用。
我提出的解决方案解决方案是启用“文本”字段,但会导致“文本”字段忽略所有键盘输入(因此,如果用户尝试使用键盘,则会产生只读行为的错觉)。最简单的方法是将__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
希望能帮助遇到同样问题的人。
答案 1 :(得分:3)
我假设这是一个更大的线程程序的一部分。
不要使用锁,而是让代码写入线程安全的队列对象。然后,在主线程中轮询队列并写入文本小部件。您可以使用事件循环(而不是编写自己的循环)通过运行轮询作业进行轮询,轮询作业重新安排自己运行几毫秒后使用after(几百毫秒可能就足够了)。