【问题标题】:Segmentation fault while redirecting sys.stdout to Tkinter.Text widget将 sys.stdout 重定向到 Tkinter.Text 小部件时出现分段错误
【发布时间】:2011-02-24 06:28:03
【问题描述】:

我正在使用基于现有 Python bdb 模块的 Python/Tkinter 构建基于 GUI 的应用程序。在此应用程序中,我想使控制台中的所有 stdout/stderr 静音并将其重定向到我的 GUI。为了实现这个目的,我编写了一个专门的 Tkinter.Text 对象(代码在文章末尾)。

基本思想是,当将某些内容写入 sys.stdout 时,它会在“文本”中显示为黑色的一行。如果将某些内容写入 sys.stderr,它将在“文本”中显示为红色的一行。写完内容后,文本总是向下滚动以查看最近的行。

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

【问题讨论】:

  • 顺便说一句,我建议不要在所有情况下都自动向下滚动。如果用户向上滚动查看某物,然后添加了一个新项目,那么当他们正在查看的内容移出视图时,他们将是一个不开心的用户。我使用的算法是,如果在输入新文本之前最后一行是可见的,我会自动滚动。否则我不会。
  • 好电话。我敢肯定,它很快就会出现在我的“待修复”列表中。

标签: python redirect segmentation-fault stdout tkinter


【解决方案1】:

我假设这是一个更大的线程程序的一部分。

不要使用锁,而是让您的代码写入线程安全的队列对象。然后,在主线程中轮询队列并写入文本小部件。您可以通过运行轮询作业来使用事件循环进行轮询(而不是编写自己的循环),该作业会重新安排自己在几毫秒后使用 after 运行(几百毫秒可能就足够了)。

【讨论】:

  • 不幸的是,我认为我还有一个更大更丑陋的问题,我仍在调试,但我确实认为这个模型是一种更清洁、更好的显示方式。感谢您的反馈。
【解决方案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=...) 调用的全部意义在于使用户无法编辑文本字段。但是,当文本字段处于 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

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

【讨论】:

  • Nash:不忽略按键,另一种方法是从小部件的绑定标签中删除“文本”。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-11-10
  • 1970-01-01
  • 2013-06-11
  • 2017-10-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-03
相关资源
最近更新 更多