Я нахожусь в процессе создавания основанного на GUI приложения с Python/Tkinter, который создает сверху существующего Python bdb модуль. В этом приложении я хочу заставить весь stdout/stderr замолчать от консоли и перенаправить его к моему GUI. Для выполнения этой цели записал я специализированный Tkinter. Текстовый объект (кодируют в конце сообщения).
Основная идея состоит в том, что, когда что-то записано в 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()
Хорошо, мне удалось отследить проблему. Мне так и не удалось воссоздать эту проблему в 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 (...)
также не работают.
Обходное решение, которое я придумал, состоит в том, чтобы оставить поле Text включенным, но заставить поле Text игнорировать весь ввод с клавиатуры (что создает иллюзию поведения только для чтения, если пользователь пытается использовать клавиатуру). Самый простой способ сделать это - изменить метод __ init __
так, чтобы он выглядел следующим образом (измените состояние на tk.NORMAL
и измените привязку для
events):
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
Надеюсь, что это поможет любому, кто столкнется с той же проблемой.
Я предполагаю, что это часть более крупной многопоточной программы.
Вместо использования блокировки пусть ваш код записывается в потокобезопасный объект очереди. Затем в основном потоке вы опрашиваете очередь и пишете в текстовый виджет. Вы можете выполнить опрос, используя цикл событий (вместо написания собственного цикла), запустив задание опроса, которое перепланировало себя для запуска через несколько мс позже с использованием after (пары сотен мс, вероятно, вполне достаточно).