Переносимый модуль запуска программ

perenosimyj modul zapuska programm Системные инструменты параллельного выполнения

Из-за всех этих различий в запуске программ на разных платформах может оказаться трудным запомнить, какие средства должны использоваться в конкретной ситуации. Более того, некоторые из этих инструментов вызываются способами, которые настолько сложны, что быстро забываются. В настоящее время существуют модули, такие как subprocess и multiprocessing, предлагающие полностью переносимые механизмы, тем не менее для конкретной платформы порой лучше подходят другие инструменты, обладающие более тонкими особенностями поведения. В Windows, например, часто бывает желательно подавить вывод окна командной оболочки.

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

В примере 5.36 приводится исходный текст модуля, в котором собрана немалая часть тех приемов, которые встретились нам в этой главе. В нем реализован абстрактный суперкласс LaunchMode, определяющий, что значит запустить программу Python, но не определяющий, как это сделать. Вместо этого его подклассы предоставляют метод run, который действительно запускает программу Python согласно выбранной схеме, и (при необходимости) определяют метод announce для вывода имени программы при запуске.

Пример 5.36. PP4E\launchmodes.py

############################################################################## запускает программы Python с помощью механизмов командной строки и классов схем запуска; автоматически вставляет pythonи/или путь к выполняемому файлу интерпретатора в начало командной строки; некоторые из инструментов в этом модуле предполагают, что выполняемый файл pythonнаходится в системном пути поиска (смотрите Launcher.py);

можно было бы использовать модуль subprocess, но он сам использует функцию os.popen(), и к тому же цель этого модуля состоит в том, чтобы запустить независимую программу, а не подключиться к ее потокам ввода-вывода;

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

новое в этом издании: при запуске сценария передает путь к файлу сценария через функцию normpath(), которая в Windows замещает все / на \; исправьте соответствующие участки программного кода в PyEdit и в других сценариях;

вообще в Windows допускается использовать / в командах открытия файлов, но этот символ может использоваться не во всех инструментах запуска программ;

##############################################################################

import sys, os

pyfile = (sys.platform[:3] == ‘win’ and ‘python.exe’) or ‘python’

pypath = sys.executable # использовать sys в последних версиях Python

def fixWindowsPath(cmdline):

замещает все / на \ в путях к сценариям в начале команд;

используется только классами, которые запускают инструменты, требующие этого в Windows; в других системах в этом нет необходимости (например, os.system в Unix);

splitline = cmdline.lstrip().split(‘ ‘) # разбить по пробелам

fixedpath = os.path.normpath(splitline[0]) # заменить прямые слешы return ‘.join([fixedpath] + splitline[1:]) # снова объединить в строку

class LaunchMode:

при вызове экземпляра класса выводится метка и запускается команда;

подклассы форматируют строки команд для метода run(), если необходимо; команда должна начинаться с имени запускаемого файла сценария Python и не должна начинаться со слова pythonили с полного пути к нему;

def __init__(self, label, command): self.what = label self.where = command

def __call__(self): # вызывается при вызове экземпляра,

self.announce(self.what) # например как обработчик щелчка на кнопке self.run(self.where) # подклассы должны определять метод run()

def announce(self, text): # подклассы могут переопределять метод print(text) # announce() вместо логики if/elif

def run(self, cmdline):

assert False, ‘run must be defined’

class System(LaunchMode):

запускает сценарий Python, указанный в команде оболочки внимание: может блокировать вызывающую программу, если в Unix не добавить &

def run(self, cmdline):

cmdline = fixWindowsPath(cmdline)

os.system(‘%s %s’ % (pypath, cmdline))

class Popen(LaunchMode):

запускает команду оболочки в новом процессе

внимание: может блокировать вызывающую программу, потому что канал закрывается немедленно

def run(self, cmdline):

cmdline = fixWindowsPath(cmdline)

os.popen(pypath + ‘ ‘ + cmdline) # предполагается, что нет данных # для чтения

class Fork(LaunchMode):

запускает команду в явно созданном новом процессе

только для Unix-подобных систем, включая cygwin

def run(self, cmdline):

assert hasattr(os, ‘fork’)

cmdline = cmdline.split() # превратить строку в список

if os.fork() == 0: # запустить новый процесс

os.execvp(pypath, [pyfile] + cmdline) # запустить новую программу

class Start(LaunchMode):

запускает команду, независимую от вызывающего процесса

только для Windows: использует ассоциации с расширениями имен файлов

def run(self, cmdline):

assert sys.platform[:3] == ‘win’

cmdline = fixWindowsPath(cmdline)

os.startfile(cmdline)

class StartArgs(LaunchMode):

только для Windows: в аргументах могут присутствовать символы прямого

слеша

def run(self, cmdline):

assert sys.platform[:3] == ‘win’

os.system(‘start ‘ + cmdline) # может создать окно консоли

class Spawn(LaunchMode):

запускает python в новом процессе, независимом от вызывающего, для Windows и Unix; используйте P_NOWAIT для окна dos;

символы прямого слеша допустимы

def run(self, cmdline):

os.spawnv(os.P_DETACH, pypath, (pyfile, cmdline))

class Top_level(LaunchMode):

запускает тот же процесс в новом окне

на будущее: требуется информация о классе графического интерфейса

def run(self, cmdline):

assert False, ‘Sorry — mode not yet implemented’

#

#   выбор “лучшего” средства запуска для данной платформы

#  возможно, выбор придется уточнить в других местах #

if sys.platform[:3] == ‘win’:

PortableLauncher = Spawn

else:

PortableLauncher = Fork

class QuietPortableLauncher(PortableLauncher) def announce(self, text):

pass

def selftest():

file = ‘echo.py’

input(‘default mode…’)

launcher = PortableLauncher(file, file)

launcher() # не блокирует

input(‘system mode…’)

System(file, file)() # блокирует

if sys.platform[:3] == ‘win’:

input(‘DOS start mode…’) # не блокирует

StartArgs(file, file)()

if __name__ == ‘__main__’: selftest()

Ближе к концу файла модуль выбирает класс по умолчанию, исходя из значения атрибута sys.platform: в Windows в атрибут PortableLauncher записывается класс, использующий spawnv, и класс, использующий комбинацию fork/exec, на других платформах. В последних версиях Python можно было бы использовать функцию spawnv на всех платформах, но альтернативные инструменты в этом модуле могут использоваться в других контекстах. Если импортировать этот модуль и всегда использовать его атрибут PortableLauncher, то можно позабыть о многочисленных специфических для платформы деталях, перечисленных в данной главе.

Чтобы запустить программу Python, просто импортируйте класс PortableLauncher, создайте экземпляр, передав метку и командную строку (без слова «python» впереди), а затем вызовите объект экземпляра, как если бы это была функция. Программа запускается операцией call методом __call__ перегрузки операторов, вместо самого метода; поэтому классы этого модуля можно также использовать для создания обработчиков обратного вызова в графических интерфейсах на базе tkinter. Как будет показано в следующих главах, нажатие кнопок в tkinter запускает вызываемый объект без аргументов. Зарегистрировав экземпляр PortableLauncher для обработки нажатия кнопки, можно автоматически запускать новую программу из графического интерфейса другой программы. Связать инструмент запуска с нажатием кнопки в графическом интерфейсе можно следующим способом:

Button(root, text=name, command=PortableLauncher(name, commandLine))

При автономном выполнении, как обычно, вызывается функция selftest этого модуля. При использовании класса System вызывающий процесс блокируется до завершения запускаемой программы, а при использовании PortableLauncher (в действительности, Spawn или Fork) и Start — нет.

C:\\PP4E> type echo.py

print(‘Spam’)

input(‘press Enter’)

C:\\PP4E> python launchmodes.py default mode

echo.py

system mode

echo.py

Spam

press Enter

DOS start mode

echo.py

Практическое применение этого файла мы увидим в главе 8, где он будет использоваться для запуска диалога с графическим интерфейсом, и в нескольких примерах в главе 10, включая PyDemos и PyGadgets, — сценарии, предназначенные для обеспечения переносимого способа запуска основных примеров в этой книге, которые находятся в вершине дерева примеров. Эти сценарии просто импортируют PortableLauncher и регистрируют экземпляры, которые будут откликаться на события в графическом интерфейсе, поэтому они прекрасно работают и в Windows, и в Unix без изменений (конечно, в этом помогает и переносимость tkinter). Сценарий PyGadgets даже настраивает PortableLauncher для изменения метки в графическом интерфейсе во время запуска.

class Launcher(launchmodes.PortableLauncher): # обертка класса запуска def announce(self, text): # изменяет метку в ГИ

Info.config(text=text)

Мы исследуем эти два и другие клиентские сценарии, такие как PyEdit во второй части книги, после того как приступим к созданию графических интерфейсов в третьей части. Отчасти из-за своей роли в сценарии PyEdit в данном издании книги этот модуль был дополнен функцией, автоматически замещающей символы прямого слеша в пути к файлу символами обратного слеша. В PyEdit в некоторых именах файлов используются символы прямого слеша, потому что они допустимы в операциях открытия файлов в системе Windows, но некоторые инструменты запуска программ в Windows требуют использования символов обратного слеша. В частности, функции system, popen и startfile из модуля os требуют использования символов обратного слеша, а функция spawnv — нет. Сценарий PyEdit и другие автоматически наследуют исправление путей к файлам в виде функции fixWindowsPath за счет импортирования и использования классов из этого модуля. Сценарий PyEdit был изменен так, чтобы устранить влияние этой функции, как неподходящее для данного конкретного случая (смотрите главу 11), но другие клиенты получают это исправление автоматически.

Обратите также внимание, что некоторые из классов в этом примере используют строку пути sys.executable, чтобы получить полный путь к выполняемому файлу Python. Отчасти это обусловлено их использованием в сценариях запуска демонстрационных примеров. В предыдущих версиях, до появления атрибута sys.executable, эти классы использовали две функции, экспортируемые модулем Launcher.py, которые отыскивали выполняемый файл интерпретатора независимо от того, поместил ли пользователь этот путь в переменную окружения PATH.

Теперь необходимость в таком поиске отпала. Поскольку я еще буду возвращаться к этому модулю в следующих главах, а также потому, что необходимость такого поиска отпала, — благодаря бесконечному потаканию Python профессиональным желаниям программистов — я отложу бессмысленные педагогические наставления в сторону. (Точка.)

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

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