Способы выполнения сторонних приложений из скрипта на Python
Запуск программ из Python: обзор методов
Для вызова внешних исполняемых файлов или системных команд Python предоставляет несколько инструментов. Наиболее современным и гибким считается модуль subprocess. В этом разделе рассмотрены разные подходы, начиная от простых и заканчивая продвинутыми.
Основной рекомендуемый способ - функция subprocess.run(). Она объединяет в себе возможности многих старых функций и добавляет безопасность при работе с аргументами.
Пример базового запуска:
import subprocess
result = subprocess.run(['python', '--version'], capture_output=True, text=True)
print(result.returncode)
print(result.stdout)Python run cmd (запуск python скриптов через cmd)
0 Python 3.12.0
запустить программу через python (запуск программы через python)
Аргумент capture_output=True перенаправляет stdout и stderr в объект результата. text=True декодирует вывод в строку (иначе возвращаются байты). Код возврата доступен как result.returncode.
Типичные ошибки:
- Неверный путь к исполняемому файлу - вызывается FileNotFoundError.
- Забыли text=True - вывод в байтах, что усложняет обработку.
- Использование shell=True с непроверенными входными данными - риск инъекции команд.
Решение: всегда передавать аргументы в виде списка, явно указывать text=True, избегать shell=True если это не критично.
Для проверки успешности выполнения используйте параметр check=True. Тогда при ненулевом коде возврата будет выброшено исключение CalledProcessError.
result = subprocess.run(['false'], check=True, capture_output=True, text=True)subprocess.CalledProcessError: Command '['false']' returned non-zero exit status 1.
Передача данных на стандартный ввод:
result = subprocess.run(['grep', 'Python'], input='Python - язык программирования', capture_output=True, text=True)
print(result.stdout)Python - язык программирования
Как выполнить команду и получить только код возврата?
Функция os.system() из модуля os - самый простой способ, но она возвращает только код возврата и не позволяет захватить вывод.
import os
code = os.system('python --version')
print(code)0
Проблема: вывод команды попадает в консоль скрипта, его невозможно обработать программно. Кроме того, os.system() использует оболочку, что несет риски безопасности.
Как захватить стандартный вывод программы в python2 стиле?
Функция os.popen() позволяет читать вывод, но использование устарело в пользу subprocess.
output = os.popen('echo Привет').read()
print(output)Привет
Недостатки: нет управления stderr, нет возможности передать данные на ввод, сложная обработка ошибок.
Как запустить команду и дождаться завершения с кодом?
Функция subprocess.call() (устаревшая) аналогична subprocess.run() без захвата вывода.
code = subprocess.call(['ls', '-l'])Она не возвращает захваченный вывод, только код. Теперь предпочтительнее использовать subprocess.run().
Как получить вывод команды и проверить успешность?
Функция subprocess.check_output() возвращает вывод, но при ошибке выбрасывает исключение.
out = subprocess.check_output(['python', '-c', 'print("hello")'])
print(out.decode()) # нужно декодироватьhello
Вывод в байтах, нет доступа к коду возврата отдельно, неудобна передача stdin.
Как запустить программу с полным контролем stdin/stdout/stderr?
Класс subprocess.Popen даёт максимальную гибкость. Подходит для интерактивных программ или длительных процессов.
proc = subprocess.Popen(['python', '-c', 'import sys; sys.stdout.write("in")'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
out, err = proc.communicate(input='data')
print(out)in
Если не вызвать communicate(), программа может зависнуть из-за заполненного буфера. Всегда закрывайте каналы или используйте communicate().
Расширенные примеры запуска программ
Запуск с переменными окружения
import subprocess, os
env = os.environ.copy()
env['MY_VAR'] = 'value'
result = subprocess.run(['env'], capture_output=True, text=True, env=env)
print('MY_VAR' in result.stdout)True
Используется копия текущего окружения, чтобы не изменить его в родительском процессе.
Параллельный запуск нескольких команд
import subprocess
processes = []
for i in range(3):
p = subprocess.Popen(['python', '-c', f'print({i})'])
processes.append(p)
for p in processes:
p.wait()
print('Готово')0 1 2 Готово
Каждый процесс работает независимо. После завершения всех - программа продолжает выполнение.
Чтение вывода в реальном времени
proc = subprocess.Popen(['python', '-u', '-c', 'import time; [print(i) or time.sleep(0.5) for i in range(5)]'], stdout=subprocess.PIPE, text=True)
for line in iter(proc.stdout.readline, ''):
print(line.strip())
proc.wait()0 1 2 3 4
Флаг -u отключает буферизацию вывода Python. iter() используется для построчного чтения до конца потока.
Передача сложных данных через stdin (байты)
proc = subprocess.Popen(['cat'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
out, _ = proc.communicate(input=b'binary data \x00 test')
print(out)b'binary data \x00 test'
Если данные содержат нулевые байты, используйте байтовый режим (text=False по умолчанию).
Ограничение времени выполнения (timeout)
try:
result = subprocess.run(['sleep', '10'], timeout=5, capture_output=True)
except subprocess.TimeoutExpired:
print('Процесс превысил время')Процесс превысил время
При срабатывании таймера процесс убивается по SIGKILL (на Unix). Для мягкого завершения используйте Popen и preexec_fn.
Запуск GUI-приложений и игнорирование вывода
subprocess.Popen(['notepad.exe'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)На Windows откроется блокнот. Вывод направляется в никуда, чтобы не блокировать консоль.
Асинхронный запуск с asyncio
import asyncio
async def run_cmd():
proc = await asyncio.create_subprocess_exec(
'python', '-c', 'print("Async")',
stdout=asyncio.subprocess.PIPE)
out, _ = await proc.communicate()
print(out.decode())
asyncio.run(run_cmd())Async
Используется asyncio.create_subprocess_exec для неблокирующего выполнения команд в асинхронном коде.