Razumevanje Podprocesa i Njihova Upotreba u Python-u
Podprocesi omogućavaju interakciju sa operativnim sistemom na potpuno novom nivou.
Naš računar neprestano pokreće podprocese. Zapravo, samo čitanjem ovog teksta pokrećete brojne procese, poput mrežnog menadžera ili samog internet pregledača.
Prednost ovoga je što svaka aktivnost koju izvršavamo na računaru uključuje pozivanje podprocesa. To važi čak i kada pišemo jednostavnu „zdravo svete“ skriptu u Python-u.
Koncept podprocesa može biti nejasan čak i ako već neko vreme učite programiranje. Ovaj članak detaljno će analizirati suštinski koncept podprocesa i način na koji se koristi Python standardna biblioteka subprocess.
Do kraja ovog uputstva, razumećete:
- Osnovni koncept podprocesa.
- Osnove Python biblioteke subprocess.
- Praktične primere Python veština kroz korisne aplikacije.
Započnimo!
Šta je Podproces?
U suštini, podproces je računarski proces kreiran od strane drugog procesa.
Podproces možemo zamisliti kao stablo, gde svaki roditeljski proces ima svoje podprocese. Znam da ovo može biti malo zbunjujuće, pa pogledajmo jednostavan grafički prikaz.
Postoji nekoliko načina za vizualizaciju procesa koji se odvijaju na našem računaru. Na primer, u UNIX sistemima (Linux i macOS) imamo htop, interaktivni preglednik procesa.
Režim stabla je najkorisniji alat za pregled aktivnih podprocesa. Možemo ga aktivirati pritiskom na F5.
Ako pažljivo pogledamo sekciju naredbi, možemo uočiti strukturu procesa koji se odvijaju na računaru.
Sve počinje sa /sbin/init, naredbom koja pokreće svaki proces na računaru. Od ove tačke, vidimo početak drugih procesa, kao što su xfce4-screenshoter i xfce4-terminal, što vodi do daljih podprocesa.
U Windows-u, imamo Task Manager, koji je koristan za zaustavljanje programa na našem računaru.
Sada imamo jasan koncept. Pogledajmo kako možemo implementirati podprocese u Python-u.
Podprocesi u Python-u
Podproces u Python-u je zadatak koji Python skripta delegira operativnom sistemu (OS).
Biblioteka subprocess omogućava nam pokretanje i upravljanje podprocesima direktno iz Python-a. Ovo uključuje rad sa standardnim ulazom (stdin), standardnim izlazom (stdout) i povratnim kodovima.
Nema potrebe da je instaliramo putem PIP-a, jer je ona deo Python standardne biblioteke.
Stoga, možemo odmah početi koristiti podprocese u Python-u jednostavnim uvozom modula.
import subprocess # Koristimo modul ....
Napomena: Da biste pratili ovaj članak, trebali biste imati Python 3.5+.
Da biste proverili verziju Python-a koju trenutno imate, pokrenite sledeću naredbu:
❯ python --version Python 3.9.5 # Moj rezultat
U slučaju da je verzija Python-a koju dobijete 2.x, možete koristiti sledeću naredbu:
python3 --version
Nastavljajući s temom, osnovna ideja biblioteke subprocess je da budemo u mogućnosti komunicirati s OS-om izvršavanjem bilo koje naredbe direktno iz Python interpretera.
To znači da možemo raditi šta god želimo, sve dok nam to OS dopušta (i dok ne uklonimo sistemske datoteke 😅).
Hajde da vidimo kako ga koristiti kreiranjem jednostavne skripte koja prikazuje datoteke u trenutnom direktorijumu.
Prva Podproces Aplikacija
Prvo, kreirajmo datoteku list_dir.py. To će biti datoteka u kojoj ćemo eksperimentisati sa listanjem datoteka.
touch list_dir.py
Sada otvorimo tu datoteku i koristimo sledeći kod:
import subprocess subprocess.run('ls')
Prvo uvozimo modul subprocess, a zatim pomoću funkcije run pokrećemo naredbu koju prosleđujemo kao argument.
Ova funkcija je uvedena u Python 3.5 kao prečica do subprocess.Popen. Funkcija subprocess.run omogućava pokretanje naredbe i čekanje njenog završetka, za razliku od Popen-a gde imamo opciju da pozovemo communicate kasnije.
Što se tiče izlaza, `ls` je UNIX naredba koja prikazuje datoteke u direktorijumu u kojem se nalazite. Stoga, ako pokrenete ovu naredbu, dobićete listu datoteka u trenutnom direktorijumu.
❯ python list_dir.py example.py LICENSE list_dir.py README.md
Napomena: Ako ste na Windows operativnom sistemu, moraćete koristiti drugačije naredbe. Na primer, umesto `ls`, možete koristiti `dir`.
Ovo se može činiti previše jednostavnim, i u pravu ste. Želite pristupiti punoj snazi koju vam školjka pruža. Stoga, naučimo kako proslediti argumente školjki putem podprocesa.
Na primer, da biste prikazali skrivene datoteke (one koje počinju tačkom), kao i sve metapodatke datoteka, pišemo sledeći kod:
import subprocess # subprocess.run('ls') # Jednostavna naredba subprocess.run('ls -la', shell=True)
Ovu naredbu pokrećemo kao string i koristimo argument shell. To znači da pozivamo školjku na početku izvršavanja našeg podprocesa, a naredbeni argument tumači direktno školjka.
Međutim, korišćenje `shell=True` ima mnoge nedostatke, a najgore su moguće sigurnosne ranjivosti. Više o tome možete pročitati u zvaničnoj dokumentaciji.
Najbolji način za prosleđivanje naredbi funkciji run je korišćenje liste, gde je `lst[0]` naredba za pozivanje (`ls` u ovom slučaju), a `lst[n]` su argumenti te naredbe.
Ako to uradimo, naš kod će izgledati ovako:
import subprocess # subprocess.run('ls') # Jednostavna naredba # subprocess.run('ls -la', shell=True) # Opasna naredba subprocess.run(['ls', '-la'])
Ako želimo sačuvati standardni izlaz podprocesa u promenljivoj, to možemo učiniti postavljanjem argumenta `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'
Za pristup izlazu procesa, koristimo atribut instance `stdout`.
U ovom slučaju, želimo sačuvati izlaz kao string, umesto bajtova, što možemo postići postavljanjem argumenta `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
Odlično, sada kada znamo osnove biblioteke subprocess, vreme je da pređemo na neke primere upotrebe.
Praktični Primeri Upotrebe Podprocesa u Python-u
U ovom delu ćemo razmotriti neke praktične primene biblioteke subprocess. Sve primere možete pronaći u ovom GitHub repozitorijumu.
Program za Proveru Dostupnosti
Jedna od glavnih upotreba ove biblioteke je mogućnost obavljanja jednostavnih OS operacija.
Na primer, jednostavna skripta koja proverava da li je program instaliran. U Linux-u, to možemo uraditi pomoću naredbe `which`.
'''Provera programa pomoću podprocesa''' import subprocess program = 'git' process = subprocess. run(['which', program], capture_output=True, text=True) if process.returncode == 0: print(f'Program "{program}" je instaliran') print(f'Lokacija binarne datoteke je: {process.stdout}') else: print(f'Žao mi je, program {program} nije instaliran') print(process.stderr)
Napomena: U UNIX sistemima, kada je naredba uspešna, njen statusni kod je 0. U suprotnom, nešto je pošlo naopako tokom izvršavanja.
Pošto ne koristimo argument `shell=True`, možemo bezbedno prihvatiti unos od korisnika. Takođe, možemo proveriti da li je unos ispravan program pomoću regularnog izraza.
import subprocess import re programs = input('Razdvojite programe razmakom: ').split() secure_pattern = '^[a-zA-Z0-9]+$' for program in programs: if not re.match(secure_pattern, program): print("Žao mi je, ne možemo proveriti taj program") continue process = subprocess. run( ['which', program], capture_output=True, text=True) if process.returncode == 0: print(f'Program "{program}" je instaliran') print(f'Lokacija binarne datoteke je: {process.stdout}') else: print(f'Žao mi je, program {program} nije instaliran') print(process.stderr) print('n')
U ovom slučaju, dobijamo programe od korisnika i koristimo regularni izraz koji potvrđuje da niz programa sadrži samo slova i brojeve. Proveravamo postojanje svakog programa pomoću for petlje.
Jednostavan Grep u Python-u
Vaš prijatelj Tom ima listu šablona u tekstualnoj datoteci i drugu veliku datoteku u kojoj želi pronaći broj podudaranja za svaki šablon. Provedeo bi sate izvršavajući naredbu grep za svaki šablon.
Srećom, vi znate kako rešiti ovaj problem pomoću Python-a i pomoći ćete mu da završi zadatak za nekoliko sekundi.
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'Šablon "{pattern}" se ne podudara ni sa jednom linijom u {readfile}') continue print(f'Šablon "{pattern}" se podudario {process.stdout.strip()} puta')
Gledajući ovu datoteku, definišemo dve varijable koje su imena datoteka sa kojima želimo raditi. Zatim otvaramo datoteku koja sadrži sve šablone i iteriramo kroz njih. Nakon toga pozivamo podproces koji izvršava naredbu grep sa zastavicom „-c“ (što znači brojanje) i detektujemo izlaz podudaranja koristeći uslov.
Ako pokrenete ovu datoteku (zapamtite da možete preuzeti tekstualne datoteke sa GitHub repo-a).
Podešavanje Virtualenv Pomoću Podprocesa
Jedna od najboljih stvari koju možete uraditi sa Python-om je automatizacija procesa. Ova vrsta skripte može vam uštedeti sate vremena svake nedelje.
Na primer, kreiraćemo skriptu za podešavanje koja kreira virtualno okruženje i pokušava pronaći datoteku requirements.txt u trenutnom direktorijumu kako bi instalirala sve zavisnosti.
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('Žao mi je, python3 nije instaliran') python_bin = process1.stdout.strip() print(f'Python pronađen u: {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'Vaše venv {VENV_NAME} je kreirano') pip_bin = f'{VENV_NAME}/bin/pip3' if Path(REQUIREMENTS).exists(): print(f'Datoteka requirements "{REQUIREMENTS}" je pronađena') print('Instaliranje zahteva') subprocess.run([pip_bin, 'install', '-r', REQUIREMENTS]) print('Proces završen! Sada aktivirajte vaše okruženje sa "source .venv/bin/activate"') else: print("Nema specificiranih zahteva...")
U ovom slučaju, koristimo više procesa i analiziramo podatke koji su nam potrebni u našoj Python skripti. Takođe koristimo pathlib biblioteku koja nam omogućava da utvrdimo da li postoji datoteka requirements.txt.
Ako pokrenete Python datoteku, dobićete neke korisne poruke o tome šta se dešava sa OS-om.
❯ python setup.py Python pronađen u: /usr/bin/python3 Vaše venv .venv je kreirano Datoteka requirements "requirements.txt" je pronađena Instaliranje zahteva Collecting asgiref==3.3.4 ....... Proces završen! Sada aktivirajte vaše okruženje sa "source .venv/bin/activate"
Imajte na umu da izlaz dobijamo iz procesa instalacije jer ne preusmeravamo standardni izlaz na varijablu.
Pokretanje Drugog Programskog Jezika
Možemo pokrenuti druge programske jezike pomoću Python-a i dobiti izlaz iz tih datoteka. To je moguće jer podprocesi direktno interaguju sa operativnim sistemom.
Na primer, hajde da napravimo program „hello world“ u C++ i Java-i. Da biste izvršili sledeće datoteke, moraćete da instalirate C++ i Java kompajlere.
helloworld.cpp
#include <iostream> int main(){ std::cout << "Ovo je hello world u C++" << std::endl; return 0; }
helloworld.java
class HelloWorld{ public static void main(String args[]){ System.out.println("Ovo je hello world u Java-i"); } }
Znam da se ovo čini kao mnogo koda u poređenju sa jednostavnom Python linijom, ali ovo je samo u svrhu testiranja.
Napravićemo Python skriptu koja pokreće sve C++ i Java datoteke u direktorijumu. Da bismo to uradili, prvo želimo dobiti listu datoteka u zavisnosti od ekstenzije datoteke, a glob nam to omogućava lako!
from glob import glob # Dobija datoteke sa svakom ekstenzijom java_files = glob('*.java') cpp_files = glob('*.cpp')
Nakon toga, možemo početi koristiti podprocese za izvršavanje svakog tipa datoteke.
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() + ' BTW, ovo je pokrenuo 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() + ' A Python podproces je ovo pokrenuo :)' print(output)
Jedan mali trik je da koristimo funkciju `strip` stringa za modifikaciju izlaza i dobijanje samo onoga što nam treba.
Napomena: Pažljivo pokrećite velike Java ili C++ datoteke jer učitavamo njihov izlaz u memoriju, što može dovesti do curenja memorije.
Otvaranje Spoljašnjih Programa
U mogućnosti smo pokrenuti druge programe samo pozivanjem njihove binarne lokacije putem podprocesa.
Hajde da to isprobamo otvaranjem Brave-a, mog omiljenog web pregledača.
import subprocess subprocess.run('brave')
Ovo će otvoriti instancu pregledača ili samo još jedan tab ako već koristite pregledač.
Kao i kod svakog drugog programa koji prihvata zastavice, možemo ih koristiti da bismo postigli željeno ponašanje.
import subprocess subprocess.run(['brave', '--incognito'])
Zaključak
Podproces je računarski proces kreiran od strane drugog procesa. Možemo proveriti procese koje naš računar pokreće pomoću alata kao što su `htop` i Task Manager.
Python ima sopstvenu biblioteku za rad s podprocesima. Trenutno, funkcija `run` daje jednostavan interfejs za kreiranje i upravljanje podprocesima.
Pomoću njih možemo kreirati bilo koju vrstu aplikacije jer smo u direktnoj interakciji sa OS-om.
Na kraju, zapamtite da je najbolji način za učenje da kreirate nešto što biste sami želeli koristiti.