Що таке підпроцес у Python? [5 Usage Examples]

Взаємодія з Операційною Системою через Підпроцеси

Підпроцеси відкривають перед нами нові горизонти взаємодії з операційною системою.

Наш комп’ютер постійно створює та запускає безліч підпроцесів. Навіть зараз, коли ви читаєте цю статтю, працює цілий ряд процесів, наприклад, мережевий менеджер і, звичайно ж, ваш інтернет-браузер.

Цікаво відзначити, що кожна наша дія на комп’ютері ініціює виклик певного підпроцесу. Це правило залишається вірним навіть для найпростіших програм, як, наприклад, сценарій “hello world” на Python.

Для деяких поняття підпроцесу може здаватися складним, навіть якщо ви вже певний час займаєтеся програмуванням. Ця стаття детально розкриє суть підпроцесів і покаже, як використовувати стандартну бібліотеку subprocess у Python.

Після прочитання цього матеріалу ви:

  • Зрозумієте, що таке підпроцес.
  • Ознайомитеся з основами бібліотеки subprocess у Python.
  • Застосуєте свої знання Python на практичних прикладах.

Отже, давайте розпочнемо.

Визначення Підпроцесу

У широкому розумінні, підпроцес – це комп’ютерний процес, який створюється іншим, вже існуючим процесом.

Можна уявити собі підпроцес як дерево, де кожен батьківський процес має дочірні процеси. Можливо, це звучить дещо заплутано, але давайте розглянемо це на простій ілюстрації.

Існують різні способи візуалізації процесів, що відбуваються на комп’ютері. Наприклад, в UNIX-подібних системах (Linux і macOS) є утиліта htop, яка дозволяє інтерактивно переглядати процеси.

Для наочного відображення запущених підпроцесів найзручніше використовувати деревоподібний режим. Його можна активувати натисканням клавіші F5.

Зверніть увагу на розділ команд. Тут ми бачимо структуру процесів, що виконуються на комп’ютері.

Все починається з /sbin/init, команди, яка ініціює запуск усіх процесів на комп’ютері. Далі ми спостерігаємо запуск інших процесів, таких як xfce4-screenshoter і xfce4-terminal (які, в свою чергу, породжують нові підпроцеси).

У Windows аналогічну функцію виконує диспетчер завдань, який допомагає зупиняти програми, що працюють некоректно.

Тепер, коли ми чітко розуміємо концепцію підпроцесів, давайте подивимося, як реалізувати їх у Python.

Підпроцеси в Python

У Python підпроцес – це завдання, яке Python-скрипт делегує операційній системі.

Бібліотека subprocess надає нам можливість запускати підпроцеси та керувати ними безпосередньо з Python. Це включає взаємодію зі стандартним введенням (stdin), стандартним виведенням (stdout) і кодами повернення.

Немає необхідності встановлювати її за допомогою pip, оскільки вона є частиною стандартної бібліотеки Python.

Щоб почати використовувати підпроцеси в Python, достатньо імпортувати модуль:

import subprocess
# Використання модуля ...

Важливо: для виконання прикладів у цій статті потрібен Python версії 3.5 або вище.

Щоб перевірити версію Python, просто виконайте команду:

❯ python --version
Python 3.9.5 # Мій результат

Якщо ви отримали версію Python 2.x, скористайтеся командою:

python3 --version

Основна ідея бібліотеки subprocess полягає у можливості взаємодіяти з операційною системою, виконуючи будь-які команди безпосередньо з інтерпретатора Python.

Це означає, що ми можемо робити все, що дозволяє наша ОС (і якщо ви випадково не видалите свою кореневу файлову систему 😅).

Давайте розглянемо, як це працює на простому прикладі, де виведемо список файлів у поточній директорії.

Перший Досвід з Підпроцесами

Створимо файл list_dir.py. Він буде використаний для експериментів зі списком файлів.

touch list_dir.py

Тепер відкрийте цей файл і скопіюйте наступний код:

import subprocess 
subprocess.run('ls')

Спочатку імпортуємо модуль subprocess, а потім використовуємо функцію run, яка запускає команду, передану їй як аргумент.

Ця функція з’явилася в Python 3.5 і є зручним скороченням для subprocess.Popen. На відміну від Popen, де ми можемо працювати з комунікацією пізніше, функція subprocess.run запускає команду і чекає її завершення.

Команда ‘ls’ в UNIX виводить список файлів в поточній директорії. Якщо ви запустите цей код, отримаєте список файлів в поточній директорії.

❯ python list_dir.py
example.py  LICENSE  list_dir.py  README.md

Примітка: у Windows замість ‘ls’ потрібно використовувати ‘dir’.

Це може здатися надто простим, і ви маєте рацію. Давайте навчимося передавати аргументи в оболонку через підпроцес.

Наприклад, щоб показати приховані файли (ті, що починаються з точки), і вивести всю метаінформацію про файли, потрібно скористатися наступним кодом:

import subprocess
# subprocess.run('ls')  # Проста команда
subprocess.run('ls -la', shell=True)

Тут ми запускаємо команду як рядок і використовуємо параметр shell=True. Це означає, що перед запуском нашого підпроцесу викликається оболонка, і аргумент команди інтерпретується безпосередньо оболонкою.

Однак використання shell=True має ряд недоліків, включаючи проблеми з безпекою. Докладніше про це можна прочитати в офіційній документації.

Найкращий спосіб передати команди функції run — це використати список, де lst[0] — команда (у цьому випадку ls), а lst[n] — її аргументи.

У такому випадку код виглядатиме так:

import subprocess
# subprocess.run('ls')  # Проста команда
# subprocess.run('ls -la', shell=True) # Небезпечна команда
subprocess.run(['ls', '-la'])

Щоб зберегти стандартний вивід підпроцесу у змінній, встановіть параметр capture_output в значення true:

list_of_files = subprocess.run(['ls', '-la'], capture_output=True)
print(list_of_files.stdout)
❯ python list_dir.py 
b'total 36ndrwxr-xr-x 3 daniel daniel 4096 may 20 21:08 .ndrwx------ 30 daniel daniel 4096 may 20 18:03 ..n-rw-r--r-- 1 daniel daniel 55 may 20 20:18 example.pyndrwxr-xr-x 8 daniel daniel 4096 may 20 17:31 .gitn-rw-r--r-- 1 daniel daniel 2160 may 17 22:23 .gitignoren-rw-r--r-- 1 daniel daniel 271 may 20 19:53 internet_checker.pyn-rw-r--r-- 1 daniel daniel 1076 may 17 22:23 LICENSEn-rw-r--r-- 1 daniel daniel 216 may 20 22:12 list_dir.pyn-rw-r--r-- 1 daniel daniel 22 may 17 22:23 README.mdn'

Доступ до результатів процесу можна отримати через атрибут stdout.

У цьому випадку ми хочемо зберегти вивід у вигляді рядка, а не байтів, тому встановлюємо параметр text в значення true.

list_of_files = subprocess.run(['ls', '-la'], capture_output=True, text=True)
print(list_of_files.stdout)
❯ python list_dir.py
total 36
drwxr-xr-x  3 daniel daniel 4096 may 20 21:08 .
drwx------ 30 daniel daniel 4096 may 20 18:03 ..
-rw-r--r--  1 daniel daniel   55 may 20 20:18 example.py
drwxr-xr-x  8 daniel daniel 4096 may 20 17:31 .git
-rw-r--r--  1 daniel daniel 2160 may 17 22:23 .gitignore
-rw-r--r--  1 daniel daniel  271 may 20 19:53 internet_checker.py
-rw-r--r--  1 daniel daniel 1076 may 17 22:23 LICENSE
-rw-r--r--  1 daniel daniel  227 may 20 22:14 list_dir.py
-rw-r--r--  1 daniel daniel   22 may 17 22:23 README.md

Чудово! Тепер, коли ми знаємо основи бібліотеки subprocess, перейдемо до практичних прикладів.

Практичні Застосування Підпроцесів у Python

У цьому розділі розглянемо практичні застосування бібліотеки subprocess. Ви можете знайти всі приклади у репозиторії Github.

Програма для Перевірки

Одним з основних застосувань цієї бібліотеки є можливість виконувати прості операції з ОС.

Наприклад, простий скрипт, що перевіряє наявність програми. У Linux це можна зробити за допомогою команди which:

'''Програма для перевірки з subprocess'''
import subprocess
program = 'git'
process = subprocess. run(['which', program], capture_output=True, text=True)
if process.returncode == 0: 
    print(f'Програма "{program}" встановлена')
    print(f'Розташування бінарного файлу: {process.stdout}')
else:
    print(f'На жаль, {program} не встановлена')
    print(process.stderr)

Примітка: в UNIX код стану успішно виконаної команди дорівнює 0. Інакше, виникає помилка.

Оскільки ми не використовуємо параметр shell=True, то можемо безпечно приймати дані користувача. Крім того, ми можемо перевірити, чи є ввід дійсною програмою за допомогою регулярного виразу.

import subprocess
import re
programs = input('Введіть програми через пробіл: ').split()
secure_pattern = 'Ср'
for program in programs:
    if not re.match(secure_pattern, program):
        print("На жаль, ми не можемо перевірити цю програму")
        continue
    process = subprocess. run(
        ['which', program], capture_output=True, text=True)
    if process.returncode == 0:
        print(f'Програма "{program}" встановлена')
        print(f'Розташування бінарного файлу: {process.stdout}')
    else:
        print(f'На жаль, {program} не встановлена')
        print(process.stderr)
    print('n')

У цьому випадку ми отримуємо програми від користувача і використовуємо регулярний вираз, який перевіряє, що рядок програми містить лише літери і цифри. Перевірку наявності кожної програми проводимо в циклі for.

Простий Grep на Python

Тому потрібна допомога. У нього є список шаблонів у текстовому файлі і великий файл, в якому потрібно порахувати кількість входжень кожного шаблону. Він вже витратив купу часу, виконуючи команду grep для кожного шаблону окремо.

На щастя, ви знаєте, як розв’язати цю проблему за допомогою Python, і допоможете йому за декілька секунд.

import subprocess
patterns_file="patterns.txt"
readfile="romeo-full.txt"
with open(patterns_file, 'r') as f:
    for pattern in f:
        pattern = pattern.strip()
        process = subprocess.run(
            ['grep', '-c', f'{pattern}', readfile], capture_output=True, text=True)
        if int(process.stdout) == 0:
            print(
                f'Шаблон "{pattern}" не знайдено в жодному рядку {readfile}')
            continue
        print(f'Шаблон "{pattern}" знайдено {process.stdout.strip()} разів')

Тут ми визначаємо дві змінні, що є іменами файлів. Відкриваємо файл з шаблонами та ітеруємося по них. Далі викликаємо підпроцес, що запускає команду grep з параметром ‘-c’ (рахувати) та перевіряємо наявність збігів.

Якщо ви запустите цей файл (текстові файли можна завантажити з Репозиторію Github)

Налаштування Віртуального Середовища через Підпроцеси

Однією з найцікавіших речей, що можна робити з Python, є автоматизація процесів. Такий скрипт може заощадити вам багато часу протягом тижня.

Наприклад, створимо скрипт налаштування, який створить для нас віртуальне середовище і спробує знайти файл requirements.txt в поточній директорії, щоб встановити залежності.

import subprocess
from pathlib import Path
VENV_NAME = '.venv'
REQUIREMENTS = 'requirements.txt'
process1 = subprocess.run(['which', 'python3'], capture_output=True, text=True)
if process1.returncode != 0:
    raise OSError('На жаль, python3 не встановлено')
python_bin = process1.stdout.strip()
print(f'Python знайдено за адресою: {python_bin}')
process2 = subprocess.run('echo "$SHELL"', shell=True, capture_output=True, text=True)
shell_bin = process2.stdout.split('/')[-1]
create_venv = subprocess.run([python_bin, '-m', 'venv', VENV_NAME], check=True)
if create_venv.returncode == 0:
    print(f'Віртуальне середовище {VENV_NAME} створено')
pip_bin = f'{VENV_NAME}/bin/pip3'
if Path(REQUIREMENTS).exists():
    print(f'Файл Requirements "{REQUIREMENTS}" знайдено')
    print('Встановлення залежностей')
    subprocess.run([pip_bin, 'install', '-r', REQUIREMENTS])
    print('Процес завершено! Активуйте віртуальне середовище за допомогою команди "source .venv/bin/activate"')
else:
    print("Файл requirements.txt не знайдено...")

У цьому випадку ми використовуємо декілька процесів та обробляємо необхідні нам дані в python-скрипті. Також використовуємо бібліотеку pathlib, яка допомагає визначити наявність файлу requirements.txt.

Запустивши цей скрипт, ви отримаєте декілька повідомлень про те, що відбувається з операційною системою.

❯ python setup.py 
Python знайдено за адресою: /usr/bin/python3
Віртуальне середовище .venv створено
Файл Requirements "requirements.txt" знайдено
Встановлення залежностей
Collecting asgiref==3.3.4 .......
Процес завершено! Активуйте віртуальне середовище за допомогою команди "source .venv/bin/activate"

Зверніть увагу, що ми отримали вивід процесу встановлення, оскільки ми не перенаправляємо стандартний вивід в змінну.

Запуск Інших Мов Програмування

За допомогою Python можна запускати інші мови програмування і отримувати вивід з цих файлів. Це можливо тому, що підпроцеси безпосередньо взаємодіють з операційною системою.

Наприклад, давайте створимо програму hello world на C++ та Java. Щоб запустити код, потрібно мати встановлені компілятор C++ та компілятор Java.

helloworld.cpp

#include <iostream>
int main(){
    std::cout << "Це hello world на C++" << std::endl;
    return 0;
}

helloworld.java

class HelloWorld{  
    public static void main(String args[]){  
     System.out.println("Це hello world на Java");  
    }  
}  

Хоча це і виглядає дещо громіздко порівняно з однорядковим кодом Python, нам це потрібно лише для демонстрації.

Далі ми напишемо скрипт Python, який запускає всі файли C++ та Java у директорії. Спочатку ми отримаємо список файлів залежно від розширення, і glob допоможе нам в цьому.

from glob import glob
# Отримання файлів з відповідним розширенням
java_files = glob('*.java')
cpp_files = glob('*.cpp')

Після цього можна починати використовувати підпроцеси для виконання кожного типу файлу.

for file in cpp_files:
    process = subprocess.run(f'g++ {file} -o out; ./out', shell=True, capture_output=True, text=True)
    output = process.stdout.strip() + ' До речі, це було запущено через Python'
    print(output)
for file in java_files:
    without_ext = file.strip('.java')
    process = subprocess.run(f'java {file}; java {without_ext}',shell=True, capture_output=True, text=True)
    output = process.stdout.strip() + ' Підпроцес Python це запустив :)'
    print(output)

Невелика хитрість — використання функцій strip для обрізання виводу і отримання потрібних даних.

Примітка: будьте обережні при запуску великих файлів Java чи C++, оскільки ми зберігаємо вихідні дані в пам’яті, що може призвести до витоку пам’яті.

Відкриття Зовнішніх Програм

За допомогою підпроцесів ми можемо запускати інші програми, просто вказуючи розташування їх виконуваних файлів.

Давайте спробуємо відкрити Brave, мій улюблений веб-браузер.

import subprocess
subprocess.run('brave')

Це відкриє нове вікно браузера або вкладку, якщо він вже запущений.

Як і з будь-якою іншою програмою, що приймає параметри, ми можемо використовувати їх для отримання бажаної поведінки.

import subprocess
subprocess.run(['brave', '--incognito'])

Підсумки

Підпроцес – це комп’ютерний процес, створений іншим процесом. Перевірити процеси, що виконуються на комп’ютері, можна за допомогою інструментів на зразок htop і диспетчера завдань.

Python має власну бібліотеку для роботи з підпроцесами. Наразі функція run надає простий інтерфейс для створення і управління підпроцесами.

З ними ми можемо створювати будь-які програми, оскільки маємо пряму взаємодію з операційною системою.

Зрештою, пам’ятайте, що найкращий спосіб навчитися – це створювати те, що вам хотілося б використовувати.