代码之家  ›  专栏  ›  技术社区  ›  Brent Writes Code

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

  •  5
  • Brent Writes Code  · 技术社区  · 14 年前

    我正在用python/tkinter构建一个基于GUI的应用程序,它构建在现有的python bdb模块之上。在这个应用程序中,我想让控制台中的所有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()
    
    2 回复  |  直到 9 年前
        1
  •  4
  •   Brent Writes Code    14 年前

    好吧,所以我设法找到了这个问题。我无法在我最初开发代码的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()
    

    我希望我能解释一下 为什么? 分段错误正在发生,但我发现不断启用和禁用文本对象是罪魁祸首。如果我将上述代码更改为:

    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(状态=…) 调用是为了让用户无法编辑文本字段。当文本字段位于 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
    

    希望能帮助遇到同样问题的人。

        2
  •  3
  •   Bryan Oakley    14 年前

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

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