Subprocesele oferă o modalitate cu totul nouă de a interacționa cu sistemul de operare al calculatorului.
Calculatorul nostru execută subprocese în mod constant. Chiar și acum, în timp ce citești acest articol, rulezi numeroase procese, cum ar fi un manager de rețea sau chiar browserul web.
Un aspect interesant este că orice acțiune pe care o realizăm pe computer implică inițierea unui subproces. Acest lucru este valabil chiar și atunci când scriem un simplu script „hello world” în Python.
Conceptul de subproces poate părea neclar chiar și pentru cei care au studiat programarea o perioadă. Acest articol va explora în detaliu conceptul principal al subprocesului și modul de utilizare a bibliotecii standard de subprocese din Python.
La finalul acestui tutorial, vei fi capabil să:
- Înțelegi conceptul de subproces.
- Cunoști elementele de bază ale bibliotecii de subprocese din Python.
- Îți exersezi abilitățile Python cu exemple practice.
Să începem!
Conceptul de subproces
În esență, un subproces este un proces informatic generat de un alt proces existent.
Ne putem gândi la un subproces ca la un arbore, unde fiecare proces principal are procese secundare care rulează în fundal. Chiar dacă pare complicat, o reprezentare grafică simplă ar putea clarifica situația.
Există mai multe moduri în care putem vizualiza procesele care rulează pe computer. De exemplu, în sistemele UNIX (Linux și macOS) există htop, un instrument interactiv de monitorizare a proceselor.
Vizualizarea sub formă de arbore este foarte utilă pentru a înțelege relația dintre procese. Aceasta poate fi activată apăsând tasta F5.
Analizând secțiunea de comandă, putem observa structura proceselor active pe calculatorul nostru.
Totul pornește de la /sbin/init, comanda care inițializează toate procesele de pe computer. De aici, vedem cum pornesc alte procese, cum ar fi xfce4-screenshoter și xfce4-terminal (care generează la rândul lor alte subprocese).
În sistemele Windows, avem utilitarul Task Manager, foarte util pentru a închide programele blocate.
Acum, conceptul este mai clar. Să vedem cum putem implementa subprocese în Python.
Subprocese în Python
Un subproces în Python reprezintă o sarcină pe care un script Python o deleagă sistemului de operare (SO).
Biblioteca de subprocese ne permite să executăm și să gestionăm subprocese direct din Python. Acest lucru presupune interacțiunea cu fluxurile standard de intrare (stdin), ieșire (stdout) și codurile de returnare.
Nu este necesar să o instalăm cu PIP, deoarece face parte din biblioteca standard Python.
Prin urmare, putem utiliza subprocese în Python doar importând modulul corespunzător.
import subprocess # Utilizarea modulului ....
Notă: Pentru a urma acest tutorial, este necesar să ai instalat Python 3.5 sau o versiune mai recentă.
Pentru a verifica versiunea de Python instalată, execută următoarea comandă:
❯ python --version Python 3.9.5 # Rezultatul meu
Dacă versiunea de Python afișată este 2.x, poți utiliza comanda:
python3 --version
Revenind la subiect, ideea principală a bibliotecii de subprocese este de a interacționa cu sistemul de operare prin executarea oricăror comenzi direct din interpretorul Python.
Aceasta înseamnă că putem face aproape orice, atât timp cât sistemul nostru de operare ne permite (și cât timp nu ștergem sistemul de fișiere rădăcină 😅).
Să vedem cum o putem folosi prin crearea unui script simplu care afișează fișierele din directorul curent.
Prima aplicație cu subproces
Mai întâi, creează un fișier numit list_dir.py. Acesta va fi fișierul în care vom testa funcționalitatea de listare a fișierelor.
touch list_dir.py
Acum, deschide fișierul și adaugă următorul cod:
import subprocess subprocess.run('ls')
În primul rând, importăm modulul subproces și apoi folosim funcția `run` care execută comanda pe care o transmitem ca argument.
Această funcție a fost introdusă în Python 3.5 ca o comandă rapidă, prietenoasă cu `subprocess.Popen`. Funcția `subprocess.run` permite executarea unei comenzi și așteaptă finalizarea acesteia, spre deosebire de `Popen`, unde avem opțiunea de a apela la comunicare mai târziu.
În ceea ce privește rezultatul codului, `ls` este o comandă UNIX care afișează fișierele din directorul curent. Prin urmare, dacă execuți această comandă, vei obține o listă cu fișierele din directorul curent.
❯ python list_dir.py example.py LICENSE list_dir.py README.md
Notă: Dacă folosești Windows, va trebui să utilizezi comenzi diferite. De exemplu, în loc de „ls” poți folosi „dir”.
Poate părea prea simplu, și ai dreptate. Vrei să folosești întreaga putere pe care o oferă shell-ul. Deci, să învățăm cum să transmitem argumente către shell cu ajutorul subproceselor.
De exemplu, pentru a lista și fișierele ascunse (cele care încep cu punct) și pentru a afișa și metadatele fișierelor, vom scrie următorul cod:
import subprocess # subprocess.run('ls') # Comanda simplă subprocess.run('ls -la', shell=True)
Rulăm această comandă ca un șir și utilizăm argumentul `shell`. Aceasta înseamnă că inițiem un shell la începutul execuției subprocesului nostru, iar argumentul comenzii este interpretat direct de shell.
Cu toate acestea, utilizarea `shell=True` are numeroase dezavantaje, cele mai grave fiind posibilele vulnerabilități de securitate. Poți citi mai multe despre ele în documentația oficială.
Cea mai bună modalitate de a transmite comenzi către funcția `run` este de a utiliza o listă, unde `lst[0]` este comanda de executat (ls în acest caz) iar `lst[n]` sunt argumentele comenzii.
Dacă facem acest lucru, codul nostru va arăta astfel:
import subprocess # subprocess.run('ls') # Comanda simplă # subprocess.run('ls -la', shell=True) # Comanda periculoasă subprocess.run(['ls', '-la'])
Dacă dorim să stocăm ieșirea standard a unui subproces într-o variabilă, putem face acest lucru setând argumentul `capture_output` la `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'
Pentru a accesa rezultatul unui proces, folosim atributul instanței `stdout`.
În acest caz, dorim să stocăm rezultatul ca șir, în loc de octeți, și putem face acest lucru setând argumentul `text` ca `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
Perfect, acum că cunoaștem elementele de bază ale bibliotecii de subprocese, este timpul să trecem la câteva exemple practice.
Exemple de utilizare a subproceselor în Python
În această secțiune, vom analiza câteva utilizări practice ale bibliotecii de subprocese. Le poți verifica pe toate în acest repozitoriu GitHub.
Verificator de programe
Una dintre principalele utilizări ale acestei biblioteci este capacitatea de a realiza operațiuni simple la nivelul sistemului de operare.
De exemplu, un script simplu care verifică dacă un program este instalat. În Linux, putem face acest lucru folosind comanda `which`.
'''Verificator de programe cu subproces''' import subprocess program = 'git' process = subprocess. run(['which', program], capture_output=True, text=True) if process.returncode == 0: print(f'Programul "{program}" este instalat') print(f'Locația binarului este: {process.stdout}') else: print(f'Îmi pare rău, programul {program} nu este instalat') print(process.stderr)
Notă: În UNIX, când o comandă are succes, codul său de stare este 0. În caz contrar, ceva a mers prost în timpul execuției.
Deoarece nu utilizăm argumentul `shell=True`, putem prelua în siguranță intrarea utilizatorului. Putem verifica și dacă intrarea este un program valid cu ajutorul unui șablon regex.
import subprocess import re programs = input('Separă programele cu un spațiu: ').split() secure_pattern = 'mar' for program in programs: if not re.match(secure_pattern, program): print("Îmi pare rău, nu putem verifica acel program") continue process = subprocess. run( ['which', program], capture_output=True, text=True) if process.returncode == 0: print(f'Programul "{program}" este instalat') print(f'Locația binarului este: {process.stdout}') else: print(f'Îmi pare rău, programul {program} nu este instalat') print(process.stderr) print('n')
În acest caz, primim programele de la utilizator și folosim o expresie regex care certifică că șirul programului include doar litere și cifre. Verificăm existența fiecărui program cu ajutorul unei bucle `for`.
Grep simplu în Python
Prietenul tău Tom are o listă de modele într-un fișier text și un alt fișier mare în care vrea să obțină numărul de potriviri pentru fiecare model. Ar petrece ore întregi rulând comanda `grep` pentru fiecare model.
Din fericire, știi cum să rezolvi această problemă cu Python și îl vei ajuta să îndeplinească această sarcină în câteva secunde.
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'Modelul "{pattern}" nu a fost găsit în nicio linie din {readfile}') continue print(f'Modelul "{pattern}" a fost găsit de {process.stdout.strip()} ori')
Analizând acest fișier, definim două variabile care sunt numele fișierelor cu care dorim să lucrăm. Apoi deschidem fișierul care conține toate modelele și iterăm prin ele. Ulterior, apelăm un subproces care execută o comandă `grep` cu indicatorul `-c` (care înseamnă număr) și afișăm rezultatul potrivirii printr-o condiție.
Dacă execuți acest fișier (reține că poți descărca fișierele text din repozitoriul GitHub menționat anterior)
Configurează un mediu virtual cu subprocese
Una dintre cele mai interesante lucruri pe care le poți face cu Python este automatizarea proceselor. Acest tip de script îți poate economisi ore de muncă pe săptămână.
De exemplu, vom crea un script de configurare care creează un mediu virtual pentru noi și încearcă să găsească un fișier `requirements.txt` în directorul curent pentru a instala toate dependențele.
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('Îmi pare rău, python3 nu este instalat') python_bin = process1.stdout.strip() print(f'Python a fost găsit în: {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'Mediul tău virtual {VENV_NAME} a fost creat') pip_bin = f'{VENV_NAME}/bin/pip3' if Path(REQUIREMENTS).exists(): print(f'Fișierul de dependențe "{REQUIREMENTS}" a fost găsit') print('Se instalează dependențele') subprocess.run([pip_bin, 'install', '-r', REQUIREMENTS]) print('Proces finalizat! Acum activează mediul cu "source .venv/bin/activate"') else: print("Nu au fost specificate dependențe...")
În acest caz, folosim mai multe procese și analizăm datele de care avem nevoie în scriptul nostru python. De asemenea, utilizăm biblioteca pathlib, care ne permite să verificăm dacă fișierul `requirements.txt` există.
Dacă execuți fișierul Python, vei primi câteva mesaje utile despre ce se întâmplă cu sistemul de operare.
❯ python setup.py Python a fost găsit în: /usr/bin/python3 Mediul tău virtual .venv a fost creat Fișierul de dependențe "requirements.txt" a fost găsit Se instalează dependențele Collecting asgiref==3.3.4 ....... Proces finalizat! Acum activează mediul cu "source .venv/bin/activate"
Reține că obținem rezultatul din procesul de instalare, deoarece nu redirecționăm ieșirea standard către o variabilă.
Execută un alt limbaj de programare
Putem executa alte limbaje de programare cu python și obține rezultatele din fișierele respective. Acest lucru este posibil deoarece subprocesele interacționează direct cu sistemul de operare.
De exemplu, să creăm un program Hello World în C++ și Java. Pentru a executa următorul fișier, va trebui să ai instalate compilatoarele C++ și Java.
helloworld.cpp
#include <iostream> int main(){ std::cout << "Acesta este un hello world în C++" << std::endl; return 0; }
helloworld.java
class HelloWorld{ public static void main(String args[]){ System.out.println("Acesta este un hello world în Java"); } }
Știu că pare mult cod în comparație cu un simplu Python, dar acest lucru este doar în scop demonstrativ.
Vom crea un script Python care execută toate fișierele C++ și Java dintr-un director. Pentru a face acest lucru, mai întâi dorim să obținem o listă de fișiere în funcție de extensia lor, iar glob ne permite să facem acest lucru cu ușurință!
from glob import glob # Obține fișierele cu fiecare extensie java_files = glob('*.java') cpp_files = glob('*.cpp')
După aceea, putem începe să utilizăm subprocese pentru a executa fiecare tip de fișier.
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() + ' Apropo, acesta a fost executat de 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() + ' Un subproces Python a executat asta :)' print(output)
Un mic truc este să folosim funcția `strip` a șirurilor pentru a modifica rezultatul și pentru a obține doar ceea ce avem nevoie.
Notă: Ai grijă să nu execuți fișiere Java sau C++ mari, deoarece încărcăm rezultatele lor în memorie, ceea ce ar putea provoca o scurgere de memorie.
Deschide programe externe
Putem rula alte programe doar apelând locația binară a acestora printr-un subproces.
Să încercăm deschizând Brave, browserul meu web preferat.
import subprocess subprocess.run('brave')
Aceasta va deschide o instanță a browserului sau doar o altă filă dacă rulezi deja browserul.
La fel ca în cazul oricărui alt program care acceptă flag-uri, le putem folosi pentru a obține comportamentul dorit.
import subprocess subprocess.run(['brave', '--incognito'])
În concluzie
Un subproces este un proces informatic creat de un alt proces. Putem verifica procesele pe care le execută calculatorul nostru cu instrumente precum htop și Task Manager.
Python are propria bibliotecă pentru a lucra cu subprocese. În prezent, funcția `run` ne oferă o interfață simplă pentru a crea și gestiona subprocese.
Putem crea orice tip de aplicație cu ele, deoarece interacționăm direct cu sistemul de operare.
Nu uita, cea mai bună modalitate de a învăța este să creezi ceva ce vrei să folosești.