15 November 2009

Разговариваем про 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. def write(self, string):
  5. if not (string == "\n"):
  6. trstring = QtGui.QApplication.translate("MainWindow", string.strip(), None, QtGui.QApplication.UnicodeUTF8)
  7. 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. def run(self):
  6. 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() — программа сообщает нам об этом. Вот такие пироги.

Собственно, работу с потоками, слотами и сигналами можно было бы рассмотреть поподробнее — но для начала вполне хватит. Надеюсь, вам понравилось наше маленькое чаепитие, до связи! Пишите комментарии :)
Tags:PythonQt4PyQt4чаепитие
Hubs: Python
+35
5.9k 61
Comments 18