Ошибка сегментации при перенаправлении sys.stdout в виджет Tkinter.Text - PullRequest
5 голосов
/ 26 мая 2010

Я нахожусь в процессе создания приложения на основе GUI с Python / Tkinter, которое основывается на существующем модуле Python bdb. В этом приложении я хочу отключить все stdout / stderr из консоли и перенаправить их в мой графический интерфейс. Для этого я написал специальный объект 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 в графический интерфейс не было бы хорошей идеей, но я все еще получаю ошибки сегментации, даже когда я перенаправляю только 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 ]

4 голосов
/ 03 июня 2010

Хорошо, так что мне удалось отследить проблему. Мне никогда не удавалось воссоздать эту проблему в 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

Надеюсь, это поможет любому, кто столкнется с той же проблемой.

3 голосов
/ 26 мая 2010

Я предполагаю, что это часть большой, многопоточной программы.

Вместо использования блокировки, пусть ваш код записывает в потокобезопасный объект очереди. Затем в своем основном потоке вы опрашиваете очередь и пишете в текстовый виджет. Вы можете выполнить опрос, используя цикл обработки событий (вместо написания своего собственного цикла), запустив задание опроса, которое перепланирует себя для запуска через несколько мс позже, используя after (вероятно, достаточно пары сотен мс).

...