Довольно часто во время использования wxPython вам нужно перенаправить stdout или stderr в текстовый контроль. Может случиться такое, что вы запустите отдельное приложение с помощью wxPython, но вам хотелось бы видеть его исходящие данные в режиме реального времени. Я лично попадал в аналогичную ситуацию несчётное количество раз, так что могу назвать её весьма распространённой. Существует несколько отличных методов перенаправления stdout.
Метод thread-safe
Первым фрагментом необходимого нам кода является класс, который мы можем использовать для того, чтобы вычленить пишущее API TextCtrl. Вот довольно стандартный пример.
1 2 3 4 5 6 7 |
class RedirectText(): def __init__(self, my_text_ctrl): self.out = my_text_ctrl def write(self,string): wx.CallAfter(self.out.WriteText, string) |
Обратите внимание на то, что в данном классе используется лишь один метод (помимо метода инициализации, разумеется). Это позволяет нам записывать текст из stdout или stderr в текстовый контроль. Стоит отметить, что этот метод записи не является thread-safe. Если вам нужно перенаправить текст из thread, нужно немного видоизменить утверждение write следующим образом:
1 2 |
def write(self, string): wx.CallAfter(self.out.WriteText, string) |
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Теперь, когда мы знаем, как записать stdout в TextCtrl, давайте попробуем самостоятельно написать немного кода, позволяющего собрать все части воедино. Вы также можете добавить следующий код в файл, содержащий класс, который был написан нами только что. Когда вы запустите код, то увидите приложение, которое выглядит примерно так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
import sys import wx class RedirectText(): def __init__(self, my_text_ctrl): self.out = my_text_ctrl def write(self,string): wx.CallAfter(self.out.WriteText, string) class MyForm(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="wxPython Redirect Tutorial") # Добавляем панель, чтобы всё отображалось корректно на всех платформах panel = wx.Panel(self, wx.ID_ANY) log = wx.TextCtrl(panel, wx.ID_ANY, size=(300,100), style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL) btn = wx.Button(panel, wx.ID_ANY, 'Push me!') self.Bind(wx.EVT_BUTTON, self.onButton, btn) # Добавляем виджеты на сайзер sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(log, 1, wx.ALL|wx.EXPAND, 5) sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5) panel.SetSizer(sizer) # Перенаправленный текст отправляется сюда redir = RedirectText(log) sys.stdout = redir def onButton(self, event): print("You pressed the button!") if __name__ == "__main__": app = wx.App(False) frame = MyForm().Show() app.MainLoop() |
В коде, расположенном вверху, я создал мультистрочный текстовый контроль, доступный только для чтения, и кнопку, главной задачей которой является вывод текста на stdout. Я добавил их в BoxSizer, чтобы защитить виджеты от напяливания друг поверх друга и для того, чтобы не возникало проблем с изменением размера рамки. Затем я инициирую класс RedirectText, пропуская его через инстанцию моего текстового контроля. Наконец, я настраиваю stdout в инстанцию RedirectText, redir (например, sys.stdout=redir).
Если вы хотите перенаправить stderr, просто добавьте следующую строку после sys.stdout=redir: sys.stderr=redir.
Вы можете улучшить данный пример изменив цвета кода, поступающего из stdout и stderr для того, чтобы быстро их различать. Эту задачу я оставил для вас, чтобы вы могли поупражняться.
Метод Non Thread-safe
Если вам не нужно беспокоится о тредах, которые пишутся в ваш TextCtrl, тогда вы можете немного упростить код благодаря тому, что TextCtrl имеет собственный метод write. Это значит, что вам больше не нужно будет использовать класс, который следует за пишущим API TextCtrl. Однако, это накладывает определённые ограничения, ввиду того, что метод write больше не встраивается в thread—safe метод wxPython, который называется: wx.CallAfter. Ладно, давайте продолжим и погрузимся в код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import sys import wx class MyForm(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="wxPython Redirect Tutorial") # Добавить панель, чтобы всё выглядело корректно на всех платфор panel = wx.Panel(self, wx.ID_ANY) style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL log = wx.TextCtrl(panel, wx.ID_ANY, size=(300,100), style=style) btn = wx.Button(panel, wx.ID_ANY, 'Push me!') self.Bind(wx.EVT_BUTTON, self.onButton, btn) # Добавить виджеты в сайзер sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(log, 1, wx.ALL|wx.EXPAND, 5) sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5) panel.SetSizer(sizer) # Перенаправить текст сюда sys.stdout = log def onButton(self, event): print("You pressed the button!") if __name__ == "__main__": app = wx.App(False) frame = MyForm().Show() app.MainLoop() |
Вы заметите, что код, расположенный выше, больше не ссылается на класс RedirectText, так как в данном случае он нам не нужен. Я почти уверен, что, если вы будете использовать threads, использование подобного подхода не будет расцениваться как thread—safe. Вам нужно переопределить метод write для TextCtrl способом, который мы обсуждали прежде, чтобы сделать его безопасным. Отдельная благодарность читателям моего блога, один из которых и натолкнул меня на эту идею.
В то же время, этот код не будет работать в wxPython Phoenix, так как wx.TextCtrl больше не имеет метода write(). Поэтому нам нужно написать версию кода, которая будет работать в Phoenix. Давайте займёмся этим!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# wxPython Phoenix import sys import wx class MyCustomTextCtrl(wx.TextCtrl): def __init__(self, *args, **kwargs): """ Инициируем текстовый контроль """ wx.TextCtrl.__init__(self, *args, **kwargs) def write(self, text): self.WriteText(text) class MyForm(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="wxPython Redirect Tutorial") # Добавляем панель так, чтобы она выглядела корректно на всех платформах panel = wx.Panel(self, wx.ID_ANY) style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL log = MyCustomTextCtrl(panel, wx.ID_ANY, size=(300,100), style=style) btn = wx.Button(panel, wx.ID_ANY, 'Push me!') self.Bind(wx.EVT_BUTTON, self.onButton, btn) # Добавляем виджеты в сайзер sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(log, 1, wx.ALL|wx.EXPAND, 5) sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5) panel.SetSizer(sizer) # Перенаправляем текст сюда sys.stdout = log def onButton(self, event): print("You pressed the button!") if __name__ == "__main__": app = wx.App(False) frame = MyForm().Show() app.MainLoop() |
Чтобы создать версию, которая будет работать на Phoenix, нам нужно превратить wx.TextCtrl в подкласс и создать собственный метод write(). Это нужно потому, что мы хотим заставить текстовый контроль вести себя так, будто он является файлоподобным объектом. Тогда мы сможем перенаправить stdout в него так, как положено. Если вы чётко следовали примеру, то заметите, что данный код работает точно также, как и пример, написанный нами для версии Classic.
Итоги
В данной статье мы рассмотрели несколько интересных тем. Мы научились перенаправлять stdout методами thread—safe и non thread—safe. Мне попадалось несколько проектов, в которых нужно было выполнить эту задачу, и все методы, перечисленные в данной статье, сработали как следует. Вы можете взять эту информацию и объединить её с информацией из других статей, чтобы перенаправлять stdout в ваши виджеты и лог-файлы.
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»