将sys.stdout重定向到Tkinter.Text小部件时出现分段错误

时间:2010-05-26 15:54:03

标签: python redirect segmentation-fault stdout tkinter

我正在使用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()

2 个答案:

答案 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(几百毫秒可能就足够了)。

相关问题