Если вернуться к главе 5 и рассмотреть реализацию модуля запуска процессов переносимым способом, использованным в примере 8.34, можно заметить, что в Windows он использует функцию os.spawnv, а в других системах — os.fork/exec. То есть графические интерфейсы в данном примере запускаются выполнением команд оболочки. Эти способы прекрасно справляются со своей задачей, но, как мы узнали в главе 5, они входят в состав более обширного набора инструментов запуска программ, в число которых также входят os.popen, os.system, os.startfile и модули subprocess и multiprocessing. Эти инструменты могут отличаться деталями подключения к окну консоли, реакцией на завершение родительского процесса и так далее.
Например, модуль multiprocessing, с которым мы познакомились в главе 5, предоставляет похожий переносимый способ запуска других графических интерфейсов в виде независимых процессов, как показано в примере 8.35. Если запустить его, он воспроизведет точно такое же окно, как на рис. 8.35, но с другими метками в главном окне.
Пример 8.35. PP4E\Gui\Tour\demoAll-prg-multi.py
4 демонстрационных класса, выполняемых как независимые процессы: multiprocessing;
модуль multiprocessing позволяет запускать только именованные функции
с аргументами — он не может работать с lambda-выражениями, поскольку в Windows они не могут быть сериализованы (глава 5); кроме того, модуль multiprocessing имеет собственные инструменты взаимодействий между процессами, такие как каналы;
from tkinter import *
from multiprocessing import Process
demoModules = [‘demoDlg’, ‘demoRadio’, ‘demoCheck’, ‘demoScale’]
def runDemo(modname): # запускается в новом процессе
module = __import__(modname) # создать GUI с нуля module.Demo().mainloop()
if __name__ == ‘__main__’:
for modname in demoModules: # только в __main__!
Process(target=runDemo, args=(modname,)).start()
root = Tk() # граф. интерфейс родительского процесса
root.title(‘Processes’)
Label(root, text=’Multiple program demo: multiprocessing’, bg=’white’).pack()
root.mainloop()
При запуске в Windows эта версия имеет только следующие функциональные отличия:
• Вывод дочерних процессов отображается в том же окне консоли, откуда был запущен этот сценарий, включая вывод, порождаемый самими демонстрационными диалогами и всеми кнопками State.
• Сценарий не завершается, если хотя бы один дочерний процесс продолжает выполнение: окно консоли в данном примере блокируется при попытке закрыть окно главного процесса, пока дочерние процессы продолжают работу, если только не установить флаг daemon дочерних процессов в значение True перед их запуском, как было показано в главе 5, — в этом случае все дочерние процессы автоматически будут завершены вместе с их родителем (но родитель по-прежнему может пережить своих потомков).
Обратите также внимание, как мы запускаем простую именованную функцию в новом процессе Process. Как мы узнали в главе 5, в Windows в аргументе target допускается передавать только сериализуемые выполняемые объекты (то есть те, которые можно импортировать), поэтому мы не можем использовать lambda-выражения для передачи дополнительных данных, как мы обычно делали это в обработчиках событий tkinter. Следующие два варианта реализации будут терпеть неудачу в Windows:
Process(target=(lambda: runDemo(modname))).start() # оба терпят неудачу!
Process(target=(lambda: __import__(modname).Demo().mainloop())).start()
Мы не будем здесь пытаться реализовать сценарий запуска программ с графическим интерфейсом всеми возможными способами, но вы можете поэкспериментировать с ними самостоятельно, используя главу 5, как источник информации по этой теме. Хотя это решение и не везде применимо, тем не менее в целом смысл использования инструментов, таких как класс PortableLauncher, — скрыть большинство таких подробностей, что позволило бы нам практически забыть о них.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011