Реализация графического интерфейса в виде отдельной программы: каналы

Объединение двух программ в предыдущем разделе напоминает программу с графическим интерфейсом, которая читает вывод команды, запущенной с помощью os.popen (или с помощью интерфейса subprocess. Popen, который опирается на эту функцию). Как будет показано далее, сокеты также поддерживают возможность обмена данными с независимыми серверами и могут использоваться для соединения программ, выполняющихся на разных компьютерах в сети, однако эту идею мы будем рассматривать в главе 12.

Пожалуй, еще более тонким и важным для нашего исследования графических интерфейсов является тот факт, что без цикла обработки событий от таймера на основе метода after и неблокирующей операции чтения данных, подобной той, что использовалась в предыдущем разделе, графический интерфейс может блокироваться в ожидании поступления данных от программы командной строки и оказаться неспособным обрабатывать более одного потока данных.

Предлагаю взглянуть на функцию redirectedGuiShellCmd в примере 10.12, перенаправляющую вывод команды оболочки, запускаемой с помощью os.popen, в окно графического интерфейса. Мы могли бы использовать простейший программный код, как в примере 10.26, чтобы перехватить вывод порожденной программы на языке Python и отобразить его в окне отдельной программы с графическим интерфейсом. Решение получилось таким компактным благодаря тому, что оно опирается на цикл чтения/записи и на класс GuiOutput из примера 10.12 для управления графическим интерфейсом и чтения данных из канала. Это решение, по сути, повторяет один из вариантов реализации самотестирования в том примере, но здесь мы читаем вывод программы на языке Python.

Пример 10.26. PP4E\Gui\Tools\pipe-gui1.py

#  графический интерфейс: перенаправляет стандартный вывод порождаемой

#  программы в окно GUI

from PP4E.Gui.Tools.guiStreams import redirectedGuiShellCmd # испет GuiOutput redirectedGuiShellCmd(‘python -u pipe-nongui.py’) # -u: без

# буферизации

Обратите внимание на ключ u командной строки интерпретатора Python, используемый здесь: он принудительно отключает буферизацию потока стандартного вывода запускаемой программы, поэтому мы получаем печатаемый текст немедленно и нам не приходится ждать завершения дочерней программы.

Мы говорили об этой возможности в главе 5, когда обсуждали каналы и состояния взаимоблокировки. Напомню, что функция print выводит текст в sys.stdout, который обычно предусматривает буферизацию при подключении к каналу таким способом. Если бы мы здесь не использовали ключ u и порожденная программа не вызывала бы метод sys. stdout.flush, мы ничего не увидели бы в графическом интерфейсе, пока дочерняя программа не завершилась бы или пока не переполнился буфер. Если дочерняя программа выполняет бесконечный цикл, нам может потребоваться ждать очень долго, пока вывод появится в канале и, соответственно, в графическом интерфейсе.

Такой подход значительно упрощает реализацию сценария командной строки, как показано в примере 10.27: он просто выводит текст в стандартный поток вывода и ему не требуется выполнять подключение к сокету. Сравните его с эквивалентной реализацией на основе сокетов, представленной в примере 10.24, — цикл тот же самый, но здесь не требуется предварительно выполнять подключение к сокету (родительская программа читает обычный поток вывода) и нет необходимости вручную выталкивать выходной буфер (ключ u, указанный при запуске дочерней программы, отключает буферизацию).

Пример 10.27. PP4E\Gui\Tools\pipe-nongui.py

#  сценарий командной строки: действует как обычно, не требует выполнения

#  дополнительных операций

import time

while True: # реализация сценария командной строки

print(time.asctime()) # отправить процессу GUI time.sleep(2.0) # выталкивать буфер здесь не требуется

Запустите сценарий графического интерфейса из примера 10.26: он автоматически запустит сценарий командной строки, подключится к его потоку стандартного вывода и отобразит окно, как показано на рис. 10.15. Своим внешним видом оно напоминает окно сценария, реализованного на основе сокетов, изображенное на рис. 10.14, но в данном случае будут выводиться строки str, которые мы получаем при чтении каналов, а не строки байтов, как при чтении из сокетов.

Рис. 10.15. Сообщения от сценария командной строки, выводимые графическим интерфейсом (каналы)

 

Сценарии действуют, но реализация графического интерфейса выглядит несколько странно — в ней отсутствует явный вызов функции mainloop, и мы получаем дополнительное пустое окно верхнего уровня по умолчанию. Фактически этот графический интерфейс действует лишь благодаря вызову метода update внутри функции перенаправления, который на мгновение передает управление циклу событий Tk, чтобы обработать ожидающие события. Более удачное решение представлено в примере 10.28. Этот сценарий создает графический интерфейс и запускает цикл событий вручную до того, как будет запущена команда оболочки, — при ее запуске воспроизводится то же самое окно (рис. 10.15).

Пример 10.28. PP4E\Gui\Tools\pipe-gui2.py

#  графический интерфейс: действует так же, как pipesgui1, но явно создает # главное окно и запускает цикл событий

from tkinter import *

from PP4E.Gui.Tools.guiStreams import redirectedGuiShellCmd

def launch():

redirectedGuiShellCmd(‘python -u pipe-nongui.py’)

window = Tk()

Button(window, text=’GO!’, command=launch).pack() window.mainloop()

Ключ u, отключающий буферизацию, здесь также имеет большое значение — без него мы не увидели бы текст в окне вывода. Графический интерфейс оказался бы заблокирован в первой операции чтения из канала, потому что текст, выводимый дочерним сценарием, оставался бы в буфере потока стандартного вывода.

С другой стороны, наличие ключа u, запрещающего буферизацию, не предотвращает блокирование графического интерфейса из предыдущего раздела, использующего сокеты, потому что в том примере после запуска дочерней программы поток вывода переключается на другой объект. Дополнительные сведения об этом приводятся в главе 12. Запомните также, что аргумент функции os.popen subprocess.Popen), определяющий параметры буферизации, управляет буферизацией только на стороне вызывающего процесса, но не в порожденной программе, тогда как ключ u передается при запуске последней.

Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011

Оцените статью
Секреты программирования
Добавить комментарий