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

Bre*_*ode 5 python redirect stdout tkinter segmentation-fault

我正在使用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()
Run Code Online (Sandbox Code Playgroud)

Bre*_*ode 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()
Run Code Online (Sandbox Code Playgroud)

我希望我能解释为什么会发生分段错误,但我发现不断启用和禁用 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()
Run Code Online (Sandbox Code Playgroud)

当我删除调用时,我的分段错误就会消失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
Run Code Online (Sandbox Code Playgroud)

希望对遇到同样问题的人有所帮助。