Cos’è un sottoprocesso in Python? [5 Usage Examples]

I sottoprocessi ti consentono di interagire a un livello completamente nuovo con il Sistema Operativo.

Il nostro computer esegue continuamente sottoprocessi. In effetti, solo leggendo questo articolo, esegui molti processi come un gestore di rete o il browser Internet stesso.

La cosa interessante di questo è che qualsiasi azione che facciamo sul nostro computer comporta l’invocazione di un sottoprocesso. Ciò rimane vero anche se stiamo scrivendo un semplice script “hello world” in Python.

Il concetto di sottoprocesso può sembrare oscuro anche se hai imparato a programmare per un po’. Questo articolo esaminerà in profondità il concetto principale del sottoprocesso e come utilizzare Python libreria standard del sottoprocesso.

Alla fine di questo tutorial:

  • Comprendere il concetto di sottoprocesso
  • Ho appreso le basi della libreria dei sottoprocessi Python
  • Hai praticato le tue abilità Python con esempi utili

Entriamo in esso

Il concetto di sottoprocesso

In generale, un sottoprocesso è a processo informatico creato da un altro processo.

Possiamo pensare a un sottoprocesso come a un albero, in cui ogni processo genitore ha processi figli in esecuzione dietro di esso. So che questo può creare confusione, ma vediamolo con un semplice grafico.

Esistono diversi modi in cui possiamo visualizzare il processo in esecuzione sul nostro computer. Ad esempio, in UNIX (Linux e MAC) abbiamo in alto, che è un visualizzatore di processi interattivo.

La modalità ad albero è lo strumento più utile per dare un’occhiata ai sottoprocessi in esecuzione. Possiamo attivarlo con F5.

Se diamo un’occhiata da vicino alla sezione dei comandi, possiamo notare la struttura dei processi in esecuzione sul nostro computer.

Tutto inizia con /sbin/init che è il comando che avvia ogni processo sul nostro computer. Da quel punto, possiamo vedere l’inizio di altri processi come xfce4-screenshoter e xfce4-terminal (che conduce a ancora più sottoprocessi)

Dando un’occhiata a Windows, abbiamo il mitico responsabile delle attività che risulta utile quando si uccidono quei programmi che vanno in crash sulla nostra macchina.

Ora abbiamo un concetto cristallino. Vediamo come possiamo implementare i sottoprocessi in Python.

Sottoprocessi in Python

Un sottoprocesso in Python è un’attività che uno script python delega al sistema operativo (OS).

La libreria dei sottoprocessi ci consente di eseguire e gestire i sottoprocessi direttamente da Python. Ciò comporta l’utilizzo dello standard input stdin, dello standard output stdout e dei codici di ritorno.

Non dobbiamo installarlo con PIP, poiché fa parte di Python libreria standard.

Pertanto possiamo iniziare a utilizzare i sottoprocessi in Python semplicemente importando il modulo.

import subprocess

# Using the module ....

Nota: per seguire questo articolo dovresti avere Python 3.5 +

Per controllare la versione di Python che hai attualmente, esegui.

❯ python --version
Python 3.9.5 # My result

Nel caso in cui la versione di Python che ottieni sia 2.x puoi usare il seguente comando

python3 --version

Continuando con l’argomento, l’idea principale alla base della libreria dei sottoprocessi è quella di poter interagire con il sistema operativo eseguendo tutti i comandi che vogliamo, direttamente dall’interprete Python.

Ciò significa che possiamo fare quello che vogliamo, fintanto che il nostro sistema operativo ce lo consente (e fintanto che non rimuovi il tuo filesystem di root 😅).

Vediamo come utilizzarlo creando un semplice script che elenca i file della directory corrente.

Prima domanda di sottoprocesso

Innanzitutto, creiamo un file list_dir.py. Questo sarà il file in cui sperimenteremo i file di elenco.

touch list_dir.py

Ora apriamo quel file e usiamo il seguente codice.

import subprocess 

subprocess.run('ls')

Per prima cosa importiamo il modulo subprocess, quindi utilizziamo la funzione run che esegue il comando che passiamo come argomento.

Questa funzione è stata introdotta in Python 3.5, come scorciatoia amichevole per sottoprocesso.Popen. La funzione subprocess.run ci consente di eseguire un comando e attendere che finisca, a differenza di Popen dove abbiamo la possibilità di chiamare la comunicazione in un secondo momento.

Parlando dell’output del codice, ls è un comando UNIX che elenca i file della directory in cui ti trovi. Pertanto, se esegui questo comando, otterrai un elenco dei file presenti nella directory corrente.

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

Nota: tieni presente che se sei in Windows, dovrai utilizzare comandi diversi. Ad esempio invece di usare “ls” puoi usare “dir”

Questo può sembrare troppo semplice, e hai ragione. Vuoi adottare un approccio completo a tutta la potenza che ti offre il guscio. Quindi impariamo come passare argomenti alla shell con subprocess.

Ad esempio per elencare anche i file nascosti (quelli che iniziano con un punto), ed elencare anche tutti i metadati dei file, scriviamo il seguente codice.

import subprocess

# subprocess.run('ls')  # Simple command

subprocess.run('ls -la', shell=True)

Stiamo eseguendo questo comando come una stringa e utilizzando la shell degli argomenti. Ciò significa che stiamo invocando una shell all’inizio dell’esecuzione del nostro sottoprocesso e l’argomento del comando viene interpretato direttamente dalla shell.

Tuttavia, l’uso shell=True ha molti aspetti negativi e il peggiore sono le possibili falle di sicurezza. Puoi leggere di loro nel documentazione ufficiale.

Il modo migliore per passare i comandi alla funzione run è utilizzare un elenco in cui lst[0] è il comando da chiamare (ls in questo caso) e lst[n] sono gli argomenti di quel comando.

Se lo facciamo, il nostro codice sarà simile a questo.

import subprocess

# subprocess.run('ls')  # Simple command

# subprocess.run('ls -la', shell=True) # Dangerous command

subprocess.run(['ls', '-la'])

Se vogliamo memorizzare lo standard output di un sottoprocesso in una variabile, possiamo farlo impostando l’argomento capture_output a 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'

Per accedere all’output di un processo, utilizziamo l’attributo di istanza stdout.

In questo caso, vogliamo memorizzare l’output come stringa, invece che come byte e possiamo farlo impostando l’argomento text come 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

Perfetto, ora che conosciamo le basi della libreria dei sottoprocessi, è ora di passare a qualche esempio di utilizzo.

Esempi di utilizzo di sottoprocessi in Python

In questa sezione esamineremo alcuni usi pratici della libreria dei processi secondari. Puoi controllarli tutti in questo Deposito Github.

Controllo del programma

Uno degli usi principali di questa libreria è la capacità di eseguire semplici operazioni del sistema operativo.

Ad esempio, un semplice script che controlla se un programma è installato. In Linux, possiamo farlo con il comando which.

'''Program checker with subprocess'''

import subprocess

program = 'git'

process = subprocess. run(['which', program], capture_output=True, text=True)

if process.returncode == 0: 
    print(f'The program "{program}" is installed')

    print(f'The location of the binary is: {process.stdout}')
else:
    print(f'Sorry the {program} is not installed')

    print(process.stderr)

Nota: In UNIX quando un comando ha successo il suo codice di stato è 0. Altrimenti, qualcosa è andato storto durante l’esecuzione

Dal momento che non stiamo usando l’argomento shell=True, possiamo prendere l’input dell’utente in modo sicuro. Inoltre, possiamo verificare se l’input è un programma valido con un pattern regex.

import subprocess

import re

programs = input('Separe the programs with a space: ').split()

secure_pattern = 'Dom'

for program in programs:

    if not re.match(secure_pattern, program):
        print("Sorry we can't check that program")

        continue

    process = subprocess. run(
        ['which', program], capture_output=True, text=True)

    if process.returncode == 0:
        print(f'The program "{program}" is installed')

        print(f'The location of the binary is: {process.stdout}')
    else:
        print(f'Sorry the {program} is not installed')

        print(process.stderr)

    print('n')

In questo caso, otteniamo i programmi dall’utente e utilizziamo un’espressione regex che certifica che la stringa del programma include solo lettere e cifre. Verifichiamo l’esistenza di ogni programma con un ciclo for a.

Grep semplice in Python

Il tuo amico Tom ha un elenco di modelli in un file di testo e un altro file di grandi dimensioni in cui desidera ottenere il numero di corrispondenze per ogni modello. Trascorreva ore a eseguire il comando grep per ogni modello.

Fortunatamente, sai come risolvere questo problema con Python e lo aiuterai a portare a termine questo compito in pochi secondi.

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'The pattern "{pattern}" did not match any line of {readfile}')

            continue

        print(f'The pattern "{pattern}" matched {process.stdout.strip()} times')

Dando un’occhiata a questo file, definiamo due variabili che sono i nomi dei file con cui vogliamo lavorare. Quindi apriamo il file che contiene tutti i modelli e iteriamo su di essi. Successivamente, chiamiamo un sottoprocesso che esegue un comando grep con il flag “-c” (means count) e determiniamo l’output della corrispondenza con un condizionale.

Se esegui questo file (ricorda che puoi scaricare i file di testo dal file Repository Github)

Imposta un virtualenv con sottoprocesso

Una delle cose più interessanti che puoi fare con Python è l’automazione dei processi. Questo tipo di script può farti risparmiare ore di tempo alla settimana.

Ad esempio, creeremo uno script di installazione che crea un ambiente virtuale per noi e cerca di trovare un file requirements.txt nella directory corrente per installare tutte le dipendenze.

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('Sorry python3 is not installed')

python_bin = process1.stdout.strip()

print(f'Python found in: {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'Your venv {VENV_NAME} has been created')

pip_bin = f'{VENV_NAME}/bin/pip3'

if Path(REQUIREMENTS).exists():
    print(f'Requirements file "{REQUIREMENTS}" found')
    print('Installing requirements')
    subprocess.run([pip_bin, 'install', '-r', REQUIREMENTS])

    print('Process completed! Now activate your environment with "source .venv/bin/activate"')

else:
    print("No requirements specified ...")

In questo caso, stiamo utilizzando più processi e analizzando i dati di cui abbiamo bisogno nel nostro script Python. Stiamo anche usando il pathlib library che ci permette di capire se il file requirements.txt esiste.

Se esegui il file python otterrai alcuni messaggi utili su ciò che sta accadendo con il sistema operativo.

❯ python setup.py 
Python found in: /usr/bin/python3
Your venv .venv has been created
Requirements file "requirements.txt" found
Installing requirements
Collecting asgiref==3.3.4 .......
Process completed! Now activate your environment with "source .venv/bin/activate"

Si noti che otteniamo l’output dal processo di installazione perché non stiamo reindirizzando l’output standard a una variabile.

Esegui un altro linguaggio di programmazione

Possiamo eseguire altri linguaggi di programmazione con Python e ottenere l’output da quei file. Questo è possibile perché i sottoprocessi interagiscono direttamente con il sistema operativo.

Ad esempio, creiamo un programma hello world in C++ e Java. Per eseguire il seguente file, dovrai installare C++ e Giava compilatori.

ciaomondo.cpp

#include <iostream>

int main(){
    std::cout << "This is a hello world in C++" << std::endl;
    return 0;
}

ciaomondo.java

class HelloWorld{  
    public static void main(String args[]){  
     System.out.println("This is a hello world in Java");  
    }  
}  

So che questo sembra un sacco di codice rispetto a un semplice one-liner Python, ma questo è solo a scopo di test.

Creeremo uno script Python che esegue tutti i file C++ e Java in una directory. Per fare questo prima vogliamo ottenere un elenco di file a seconda dell’estensione del file e globo ci permette di farlo facilmente!

from glob import glob

# Gets files with each extension
java_files = glob('*.java')

cpp_files = glob('*.cpp')

Successivamente, possiamo iniziare a utilizzare i sottoprocessi per eseguire ogni tipo di file.

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 this was runned by 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 subprocess runned this :)'
    print(output)

Un piccolo trucco consiste nell’usare la funzione stringa strip per modificare l’output e ottenere solo ciò di cui abbiamo bisogno.

Nota: fai attenzione a eseguire file Java o C++ di grandi dimensioni poiché stiamo caricando il loro output in memoria e ciò potrebbe produrre una perdita di memoria.

Apri programmi esterni

Siamo in grado di eseguire altri programmi semplicemente chiamando la loro posizione binari attraverso un sottoprocesso.

Proviamo aprendo coraggioso, il mio browser web preferito.

import subprocess

subprocess.run('brave')

Questo aprirà un’istanza del browser o solo un’altra scheda se hai già eseguito il browser.

Come con qualsiasi altro programma che accetta i flag, possiamo usarli per produrre il comportamento desiderato.

import subprocess

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

Per riassumere

Un processo secondario è un processo informatico creato da un altro processo. Possiamo controllare i processi in esecuzione sul nostro computer con strumenti come htop e il task manager.

Python ha la sua libreria per lavorare con i sottoprocessi. Attualmente, la funzione run ci offre una semplice interfaccia per creare e gestire i sottoprocessi.

Possiamo creare qualsiasi tipo di applicazione con loro perché stiamo interagendo direttamente con il sistema operativo.

Infine, ricorda che il modo migliore per imparare è creare qualcosa che vorresti usare.