Как стать автором
Обновить

Разговариваем про PyQt4 — Посиделка вторая

Python *
image

Добро пожаловать!


    В прошлый раз мы обсуждали, как можно писать свое PyQt4-приложение, опираясь на логику сгенерированного программкой pyuic4 файла. Как это часто бывает — после написания топик получил много интересных и, что самое главное, содержательных комментариев, объясняющих, почему в отдельных случаях я прав, а в других неправ.
    Самое любопытное состоит еще и в том, что обсуждение интересно как для питонистов, так и для приверженцев C++, ибо в данном случае разница невелика, в основном только незначительные вещи в синтаксисе. Все это потому, что PyQt4, по своей сути, является простой оберткой вокруг сишных Qt-классов, сохраняющей все названия и методы. Итак, вот вам чашечка чая или кофе, устраивайтесь поудобнее, давайте начнем нашу беседу.

С места в карьер


    Итак, мы пишем оконное приложение. Акцент здесь на слове "оконное", а это значит, что у нас есть user-friendly интерфейс, позволяющий конечному пользователю оценить всю прелесть щелканья по кнопочкам. Таким образом, консолью мы пользуемся для отладки и отлова ошибок. Но зачем консоль юзеру, у которого уже есть красивое окошко? Тут можно выбирать, что делать для удобства — вести лог работы (этот способ как раз любит Microsoft, собирая в логи все самое интересное и спрашивая, отправлять ли отчет о работе приложения) или перенаправить вывод консоли на виджет на форме, чтобы видеть, с чем имеешь дело. И если первое делается довольно легко:
Copy Source | Copy HTML
  1. import sys
  2. # открываем файлы для записи с возможностью аппендинга нужной информации
  3. sys.stdout = open("log.txt", "a+")
  4. sys.stderr = open("errors.txt", "a+")

то со вторым у многих возникают вопросы. На самом деле отгадка скрыта как раз в этом примере. Что мы имеем? File-like-объект, позволяющий добавление строк в конец. Это делается, насколько мы помним из работы с файлами в Python, с помощью метода write(). Так давайте сделаем объект с методом write() и передадим ему в качестве параметра выводной виджет!
Copy Source | Copy HTML
  1. class Logger(object):
  2.     def __init__(self, output):
  3.         self.output = output
  4.  
  5.     def write(self, string):
  6.         if not (string == "\n"):
  7.             trstring = QtGui.QApplication.translate("MainWindow", string.strip(), None, QtGui.QApplication.UnicodeUTF8)
  8.             self.output.append(trstring)

при этом зачастую приходится отрезать от строки все ненужное и делать проверку на символ переноса каретки, чтобы половину лога не занимали пустые строки.
    Теперь, чтобы перенаправить вывод консоли в виджет, нам надо прописать его в классе окна и привязать через класс логгера к стандартному выводу:
Copy Source | Copy HTML
  1. self.logText = QtGui.QTextEdit(MainWindow)
  2. # даем виджету свойство read-only
  3. self.logText.setReadOnly(True)
  4. # делаем полосу вертикальной прокрутки видимой всегда, на мой взгляд, так удобнее
  5. self.logText.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
  6. # начальное сообщение
  7. self.logText.append("Log started")
  8. # можно как перенаправлять все в один виджет, так и разделить ошибки и 
  9. # стандартный вывод
  10. self.logger = Logger(self.logText)
  11. self.errors = Logger(self.logText)
  12. sys.stdout = self.logger
  13. sys.stderr = self.errors

    Теперь мы можем писать в лог двумя способами — либо (в классе окна) с помощью self.logger.write(), либо просто с помощью print. Таким образом, сделав красивый вывод ошибок при try-except'ах, мы видим все прямо в окне. Тут еще большим плюсом является то самое разделение Python-логики с логикой Qt — форма висит в отдельном потоке и при ошибках во внешних функциях приложение не завершается, а вот ошибка сразу падает в лог. И да, об этом стоит поговорить немного подробнее.

Чуть-чуть о потоках


    Думаю, вы не раз замечали, что если прописать функцию, требующую много времени и ресурсов на выполнение, то форма «подвиснет» и никаких данных о состоянии функции вы не получите. Я сам столкнулся с этим, когда хотел сделать последовательный вывод возвращаемых из функций значений в список на форме. Решение же очевидное — использовать потоки. Причем перед нами будет стоять выбор — использовать потоки с Python-логикой или же потоки Qt? Лично я в данном случае предпочитаю второе — меньше мороки в некоторых аспектах.
    Для того, чтобы нам было красиво, надо опять создать очередной класс с наследованием от Qt-шного. А что поделать, такая уж «классовая логика» :) Тут свои плюсы и минусы, но сейчас не об этом.
Copy Source | Copy HTML
  1. class myQThread(QtCore.QThread):
  2.     def __init__(self, output):
  3.         QtCore.QThread.__init__(self)
  4.         self.output = output
  5.  
  6.     def run(self):
  7.         some_big_function_with_output()

    Как видите, сначала мы инициализируем класс Qt-шного потока (я еще добавил в параметры конструктора объект вывода — это как раз может быть тот самый виджет на форме). Затем переопределяем стандартный метод run(), в котором как раз и содержится то, что должен делать поток. Эту функцию или функции можно подключать даже из внешних модулей — в данном случае это не играет роли. Ее вывод в данном случае можно и нужно осуществить через передаваемый функции output с помощью все того же метода append(). Вы можете для пущей красоты добавить прогрессбар или другой визуализатор, чтобы пользователь видел, что поток работает.
    А как мы узнаем, что поток завершил свою работу? Для этого намутим один небольшой хак — мы создадим слот обработчика сигнала от потока, но не будем применять connect, а используем декоратор:
Copy Source | Copy HTML
  1. @QtCore.pyqtSignature("")
  2. def threadFinished(self):
  3.     self.logger.write("Поток завершен!")

    Да-да, вы правильно поняли, это пишется именно в класс формы, а когда генерируется сигнал threadFinished() — программа сообщает нам об этом. Вот такие пироги.

    Собственно, работу с потоками, слотами и сигналами можно было бы рассмотреть поподробнее — но для начала вполне хватит. Надеюсь, вам понравилось наше маленькое чаепитие, до связи! Пишите комментарии :)
Теги:
Хабы:
Всего голосов 47: ↑41 и ↓6 +35
Просмотры 6K
Комментарии Комментарии 18

Работа

Data Scientist
127 вакансий
Python разработчик
218 вакансий