Modul subprocess je dnes preferovaný (a podstatně bezpečnější) způsob práce s externími programy. Od Python'u 3.5 se jedná především o funkci run(), ve starších verzích nebo pro speciálnější použití pak o obecnou třídu Popen().
Dále máme k dispozici několik užitečných konstant (subprocess.PIPE
, subprocess.STDOUT
, subprocess.DEVNULL
) a také přehršel dalších pomocných funkcí (dnes už převážně nedoporučovaných).
PS: Dříve se pro dosažení podobného efektu používala především funkce os.system() a armáda funkcí os.spawn*() (případně i os.exec*(), ty ovšem kompletně nahrazovaly aktuální proces volaným a nevracely se z něj).
Dnes preferovaná metoda spouštění externích procesů (a získávání jejich výsledků) je pomocí funkce run(), která má v nejobvyklejších případech následující argumenty:
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None)
Tato funkce zavolá externí proces a čeká na jeho ukončení. Po návratu do volajícího prostředí vrátí instanci třídy CompletedProcess naplněnou návratovými hodnotami z proběhlého externího procesu.
PS: Ve skutečnosti jsou parametry funkce run() až na tři (input, timeout, check) stejné jako u Popen() a také se volají jejich přímým předáním na ni.
Nejjednodušší způsob použití představuje přímé zavolání externího programu bez jakýchkoli parametrů..
..případně s parametry:
Výstup programu se v tomto případě „vysype“ tam, kam má Python zrovna přístup, nejčastěji tedy na konzoli.
PS: Mimochodem na první pohled zdánlivě nešikovné uvádění parametrů jako seznamu řetězců má tu výhodu, že se vyhneme většině trablů spojených se správným iskejpováním vstupu na příkazové řádce.
Výstup externího programu si můžeme snadno přesměrovat z výchozího standardního výstupu třebas do souboru pomocí atributu stdout:
Vstup pro externí program můžeme zařídit pomocí specifického parametru input=b'':
Python 3.6 s sebou přinesl velmi pohodlné zjednodušení – nastavení parametru encoding způsobí změnu vyhodnocování proudů stdin, stdout a stderr z bajtových řetězců na příslušné textové:
PS: Kromě toho můžete pomocí parametru errors nastavit i vlastní obsluhu chyb vzniklých při (de)kódování takto vzniklých řetězcových dat. V podstatě pro překlad z bajtů na řetězce stačí, aby byl nastavený alespoň jeden z těchto parametrů.
Návratový objekt ukončeného externího procesu má několik užitečných vlastností:
shell=True
je zde nastaven proto, aby se vstupní řetězec "ls; exit 1"
správně vyhodnotil – bez něj se Python přímo pokusí zavolat zadaný řetězec, což neprojde, zatímco s ním Python spustí shell (na Linuxu typicky /bin/sh), v jehož kontextu kód zavolá.
shell=True
(a os.system()
) na neošetřený uživatelský vstup, je tak z hlediska bezpečnosti jedna z nejhorších věcí, co vůbec můžete v Python'u udělat… Pro podobné případy nezapomeňte alespoň vstup náležitě odiskejpovat pomocí shlex.quote()
.
Případná chyba ve volání externího procesu se tedy projeví především v nastavení parametru CompletedProcess.returncode
. Chceme-li na ni nějak zareagovat, musíme nastavit parametr volání check=True, což způsobí vyhození výjimky:
Pokud nechceme (nebo naopak chceme), aby se případné chyby z externího procesu objevily mezi jeho normálním výstupem, stačí přesměrovat chybový výstup pomocí parametru stderr:
stderr=subprocess.PIPE
zachytil, abych si ho mohl přečíst v návratovém objektu.
Pro případ, že by se volání externího procesu „zadrhlo“, je k dispozici omezovač v podobě atributu timeout=SEKUND, který zajistí vyhození výjimky subprocess.TimeoutExpired
:
Ve skutečnosti za veškerou funkcionalitou stojí úplně základní třída Popen, která – až na zmíněné tři parametry – vlastně zpracovává volání funkce run():
subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=(), *, encoding=None, errors=None)
Až do Python'u 3.4 to byl i preferovaný způsob volání externích procesů, nicméně dnes je tu především pro ty, kterým z nějakého důvodu nestačí run(). Oproti němu se liší především ve způsobu, jakým se zahajuje zpracování externího procesu a jak se mu předávají parametry.
Volání externích procesů je dosti flexibilní – s výhodou můžeme použít zvláště následující:
stdin
, stdout
a stderr
, které slouží k přesměrování vstupu, výstupu a chybového výstupu volaného procesu.
cwd
slouží ke změně adresáře, v němž dojde k zavolání externího procesu.
env
slouží k modifikaci proměnných prostředí pro potřeby spouštěného externího procesu.
subprocess.PIPE
zajišťuje metoda communicate([input=None])
. Jejím úkolem je posílat data do stdin (v tom případě vyžaduje nastavení stdin=subprocess.PIPE
) a číst je ze stdout a stderr (čeká přitom na ukončení volaného procesu).
None
(tj. žádná vstupní data), jinak to musí být bajtový řetězec. (Což je v ostrém kontrastu s Pythonem 2.x – tam to byl řetězec. Změna je to ale pochopitelná, protože Python 3.x narozdíl od Python'u 2.x striktně rozlišuje mezi binárními 8-bitovými daty a řetězci.)
communicate()
posílat nic a vstup do programu zařídit například ze souboru pomocí stdin=open()
a podobně.
PS: Samozřejmě nezapomeňte na číhající bezpečností problém s parametrem shell
!
Vyvolání externího programu se vstupem ze standardního proudu a výstupem do souboru:
Složitější varianta předchozího – standardní i chybový výstup přesměrovány také na standardní proud:
Příklad zachycení chybového hlášení:
Návratový kód ukončivšího se programu se pochopitelně schovává ve vlastnosti returncode volaného procesu: