Zrozumienie i wykorzystanie podprocesów w Pythonie
Podprocesy umożliwiają interakcję z systemem operacyjnym w sposób znacznie bardziej zaawansowany i elastyczny.
Twój komputer nieustannie uruchamia podprocesy. W rzeczywistości, samo przeglądanie tego artykułu aktywuje wiele z nich, takich jak menedżer sieci czy przeglądarka internetowa.
Każde działanie, które wykonujesz na komputerze, wymaga uruchomienia podprocesu. To dotyczy nawet tak prostych zadań jak uruchomienie skryptu „hello world” w Pythonie.
Koncepcja podprocesu może być niejasna, nawet jeśli masz już pewne doświadczenie w programowaniu. W tym tekście przyjrzymy się bliżej idei podprocesów i sposobom ich obsługi za pomocą standardowej biblioteki subprocess
w Pythonie.
Po przeczytaniu tego artykułu, będziesz w stanie:
- Zrozumieć, czym jest podproces.
- Poznać fundamenty biblioteki
subprocess
w Pythonie. - Wykorzystać zdobytą wiedzę w praktycznych przykładach.
Zacznijmy!
Definicja podprocesu
Podproces to zasadniczo proces obliczeniowy, który został utworzony przez inny proces.
Można wyobrazić sobie podprocesy jako drzewo, gdzie procesy potomne są uruchamiane przez procesy nadrzędne. Ta koncepcja może być początkowo nieco skomplikowana, ale ilustracja graficzna powinna pomóc ją zrozumieć.
Istnieje kilka sposobów wizualizacji procesów działających na komputerze. Na przykład w systemach UNIX (Linux i MAC) popularne jest interaktywne narzędzie do przeglądania procesów htop.
Tryb drzewa (aktywacja klawiszem F5) jest szczególnie przydatny, aby zobaczyć hierarchię uruchomionych podprocesów.
Analizując sekcję poleceń, można zobaczyć strukturę procesów działających na komputerze.
Wszystko zaczyna się od /sbin/init, polecenia inicjującego każdy proces na komputerze. Następnie widać kolejne procesy, takie jak xfce4-screenshoter i xfce4-terminal, które same uruchamiają dodatkowe podprocesy.
W systemie Windows odpowiednikiem jest Menedżer Zadań, który jest przydatny do zamykania awaryjnie działających programów.
Teraz, gdy koncepcja podprocesu jest bardziej klarowna, przejdźmy do implementacji podprocesów w Pythonie.
Wykorzystanie podprocesów w Pythonie
W Pythonie podproces to zadanie, które skrypt deleguje do systemu operacyjnego.
Biblioteka subprocess
umożliwia wykonywanie podprocesów i zarządzanie nimi bezpośrednio z poziomu Pythona, w tym obsługę standardowego wejścia (stdin), standardowego wyjścia (stdout) oraz kodów powrotu.
Biblioteka ta nie wymaga instalacji za pomocą pip
, ponieważ stanowi część standardowej biblioteki Pythona.
Aby zacząć korzystać z podprocesów, wystarczy zaimportować moduł:
import subprocess # Wykorzystanie modułu...
Uwaga: Ten artykuł zakłada, że używasz Pythona w wersji 3.5 lub wyższej.
Aby sprawdzić aktualną wersję Pythona, wykonaj:
❯ python --version Python 3.9.5 # Wynik przykładowy
Jeśli masz starszą wersję Pythona 2.x, użyj następującego polecenia:
python3 --version
Głównym celem biblioteki subprocess
jest interakcja z systemem operacyjnym poprzez wykonywanie dowolnych poleceń bezpośrednio z interpretera Pythona. Oznacza to, że możesz wykonać każde zadanie, na które pozwala twój system operacyjny (oczywiście, z pewnymi ograniczeniami).
Zobaczmy, jak to działa na przykładzie prostego skryptu, który wyświetla listę plików w bieżącym katalogu.
Pierwsza aplikacja z użyciem podprocesu
Na początek stwórz nowy plik list_dir.py
. To w nim będziemy eksperymentować z listowaniem plików.
touch list_dir.py
Teraz otwórz plik i wklej poniższy kod:
import subprocess subprocess.run('ls')
Najpierw importujemy moduł subprocess
, a następnie za pomocą funkcji run
uruchamiamy przekazane w argumencie polecenie.
Funkcja subprocess.run
została wprowadzona w Pythonie 3.5 jako wygodne uproszczenie subprocess.Popen
. run
uruchamia polecenie i czeka na jego zakończenie, natomiast Popen
daje możliwość późniejszej komunikacji z procesem.
Polecenie ls
(w systemach UNIX) wyświetla listę plików w bieżącym katalogu. Uruchomienie skryptu pokaże więc zawartość twojego katalogu.
❯ python list_dir.py example.py LICENSE list_dir.py README.md
Uwaga: jeśli pracujesz w systemie Windows, zamiast ls
użyj polecenia dir
.
To może wydawać się zbyt proste. Aby wykorzystać w pełni potencjał powłoki, nauczmy się przekazywać argumenty do niej za pomocą subprocess
.
Aby na przykład wyświetlić ukryte pliki (zaczynające się od kropki) wraz z metadanymi, napisz kod:
import subprocess # subprocess.run('ls') # Proste polecenie subprocess.run('ls -la', shell=True)
W tym przypadku uruchamiamy polecenie jako ciąg znaków, używając argumentu shell=True
. Oznacza to, że przed wykonaniem naszego podprocesu, powłoka jest wywoływana, a argument polecenia jest interpretowany bezpośrednio przez nią.
Użycie shell=True
niesie jednak ryzyko, w tym zagrożenia bezpieczeństwa, które zostały opisane w oficjalnej dokumentacji.
Najbezpieczniejszym sposobem przekazywania poleceń do funkcji run
jest użycie listy, gdzie pierwszy element to polecenie, a kolejne to jego argumenty.
Kod z użyciem listy wygląda następująco:
import subprocess # subprocess.run('ls') # Proste polecenie # subprocess.run('ls -la', shell=True) # Niebezpieczne polecenie subprocess.run(['ls', '-la'])
Aby zapisać standardowe wyjście podprocesu w zmiennej, ustaw parametr capture_output
na 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'
Dostęp do wyjścia procesu uzyskujemy poprzez atrybut stdout
instancji.
Aby zapisać wyjście jako ciąg znaków zamiast bajtów, ustaw argument text
na 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
Teraz, gdy znasz podstawy biblioteki subprocess
, przejdźmy do przykładów jej praktycznego zastosowania.
Praktyczne zastosowania podprocesów w Pythonie
W tej części omówimy kilka zastosowań biblioteki subprocess
. Wszystkie przykłady możesz znaleźć w tym repozytorium GitHub.
Kontrola zainstalowanych programów
Jednym z głównych zastosowań tej biblioteki jest wykonywanie prostych zadań w systemie operacyjnym.
Na przykład, prosty skrypt, który sprawdza, czy dany program jest zainstalowany. W systemie Linux można to zrobić za pomocą polecenia which
.
'''Kontrola programów z użyciem subprocess''' import subprocess program = 'git' process = subprocess. run(['which', program], capture_output=True, text=True) if process.returncode == 0: print(f'Program "{program}" jest zainstalowany') print(f'Lokalizacja pliku wykonywalnego: {process.stdout}') else: print(f'Program {program} nie jest zainstalowany') print(process.stderr)
Uwaga: W systemie UNIX kod statusu 0 oznacza, że polecenie zakończyło się pomyślnie. Inne kody statusu sygnalizują błąd.
Ponieważ nie używamy shell=True
, możemy bezpiecznie przetwarzać dane wprowadzone przez użytkownika. Możemy również sprawdzić poprawność danych wejściowych za pomocą wyrażenia regularnego.
import subprocess import re programs = input('Podaj nazwy programów oddzielone spacją: ').split() secure_pattern = 'pon.' for program in programs: if not re.match(secure_pattern, program): print("Nie możemy sprawdzić tego programu") continue process = subprocess. run( ['which', program], capture_output=True, text=True) if process.returncode == 0: print(f'Program "{program}" jest zainstalowany') print(f'Lokalizacja pliku wykonywalnego: {process.stdout}') else: print(f'Program {program} nie jest zainstalowany') print(process.stderr) print('n')
W tym przypadku pobieramy listę programów od użytkownika i sprawdzamy poprawność nazwy. Następnie sprawdzamy obecność każdego programu w pętli for
.
Prosty odpowiednik polecenia „grep” w Pythonie
Twój kolega Tomek ma listę wzorców w pliku tekstowym i chce znaleźć liczbę dopasowań dla każdego wzorca w innym dużym pliku. Do tej pory spędzał godziny na uruchamianiu polecenia grep dla każdego wzorca oddzielnie.
Na szczęście, Ty wiesz, jak rozwiązać ten problem w kilka sekund przy użyciu Pythona.
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'Wzorzec "{pattern}" nie został dopasowany w pliku {readfile}') continue print(f'Wzorzec "{pattern}" został dopasowany {process.stdout.strip()} razy')
W tym kodzie definiujemy zmienne przechowujące nazwy plików. Następnie otwieramy plik zawierający wzorce, iterujemy po nich, wywołujemy podproces, który uruchamia polecenie grep
z flagą -c
(zliczanie dopasowań). Wynik dopasowania jest analizowany za pomocą warunku.
Jeśli uruchomisz ten kod, pamiętaj, że pliki tekstowe możesz pobrać z repozytorium Github.
Konfiguracja środowiska virtualenv z użyciem podprocesów
Python pozwala automatyzować procesy, co może zaoszczędzić wiele godzin pracy w ciągu tygodnia. Pokażemy przykład skryptu, który utworzy środowisko wirtualne i zainstaluje zależności z pliku requirements.txt
(jeśli istnieje).
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 nie jest zainstalowany') python_bin = process1.stdout.strip() print(f'Python znaleziony w: {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'Środowisko {VENV_NAME} zostało utworzone') pip_bin = f'{VENV_NAME}/bin/pip3' if Path(REQUIREMENTS).exists(): print(f'Plik requirements "{REQUIREMENTS}" znaleziony') print('Instalowanie zależności') subprocess.run([pip_bin, 'install', '-r', REQUIREMENTS]) print('Proces zakończony! Aktywuj środowisko za pomocą: "source .venv/bin/activate"') else: print("Brak pliku requirements...")
W tym skrypcie wykorzystujemy kilka procesów i analizujemy ich wyniki w Pythonie. Korzystamy też z biblioteki pathlib, która ułatwia sprawdzanie, czy plik requirements.txt
istnieje.
Uruchomienie skryptu wyświetli komunikaty o postępie procesu.
❯ python setup.py Python znaleziony w: /usr/bin/python3 Środowisko .venv zostało utworzone Plik requirements "requirements.txt" znaleziony Instalowanie zależności Collecting asgiref==3.3.4 ....... Proces zakończony! Aktywuj środowisko za pomocą: "source .venv/bin/activate"
Wyjście procesu instalacji nie jest przekierowywane do zmiennej, dlatego widzimy je bezpośrednio.
Uruchamianie programów w innych językach
Podprocesy umożliwiają uruchamianie programów w innych językach i pobieranie ich danych wyjściowych, ponieważ wchodzą w bezpośrednią interakcję z systemem operacyjnym.
Poniżej znajdują się przykładowe programy „hello world” w C++ i Javie. Aby uruchomić skrypt Pythona, musisz mieć zainstalowane kompilatory C++ i Javy.
helloworld.cpp
#include <iostream> int main(){ std::cout << "Hello world z C++" << std::endl; return 0; }
helloworld.java
class HelloWorld{ public static void main(String args[]){ System.out.println("Hello world z Javy"); } }
Choć kody w C++ i Java są dłuższe niż w Pythonie, są one użyte tylko do celów demonstracyjnych.
Stwórzmy teraz skrypt Pythona, który uruchomi wszystkie pliki C++ i Java w katalogu. Najpierw uzyskamy listę plików, korzystając z modułu glob.
from glob import glob # Pobierz pliki z odpowiednimi rozszerzeniami java_files = glob('*.java') cpp_files = glob('*.cpp')
Teraz możemy użyć podprocesów do uruchomienia każdego typu pliku.
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() + ' - uruchomione przez Pythona' 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() + ' - to również uruchomił Python :)' print(output)
Wykorzystaliśmy metodę strip()
, aby zmodyfikować dane wyjściowe i uzyskać tylko potrzebne informacje.
Uwaga: Uruchamiaj duże pliki Java i C++ ostrożnie, ponieważ dane wyjściowe są ładowane do pamięci, co może prowadzić do jej wycieku.
Uruchamianie programów zewnętrznych
Możemy uruchamiać inne programy, wywołując ich pliki wykonywalne za pośrednictwem podprocesów.
Sprawdźmy, jak to działa, uruchamiając przeglądarkę internetową Brave.
import subprocess subprocess.run('brave')
To spowoduje otwarcie nowej instancji przeglądarki lub nowej karty, jeśli jest już uruchomiona.
Jak w przypadku każdego programu, możemy użyć jego flag, aby uzyskać pożądane działanie.
import subprocess subprocess.run(['brave', '--incognito'])
Podsumowanie
Podproces to proces komputerowy, który został utworzony przez inny proces. Można monitorować uruchomione procesy, korzystając z narzędzi takich jak htop
lub Menedżer Zadań.
Python udostępnia bibliotekę do zarządzania podprocesami. Funkcja run
ułatwia tworzenie i kontrolę podprocesów.
Wykorzystując podprocesy, można tworzyć różnorodne aplikacje, które bezpośrednio komunikują się z systemem operacyjnym.
Pamiętaj, że najlepszą metodą nauki jest tworzenie praktycznych aplikacji.
newsblog.pl
Maciej – redaktor, pasjonat technologii i samozwańczy pogromca błędów w systemie Windows. Zna Linuxa lepiej niż własną lodówkę, a kawa to jego główne źródło zasilania. Pisze, testuje, naprawia – i czasem nawet wyłącza i włącza ponownie. W wolnych chwilach udaje, że odpoczywa, ale i tak kończy z laptopem na kolanach.