Qu’est-ce qu’un sous-processus en Python ? [5 Usage Examples]

Photo of author

By pierre



Les sous-processus ouvrent une voie inédite pour interagir avec le système d’exploitation de votre machine.

Votre ordinateur lance constamment des sous-processus. Rien qu’en lisant cet article, de multiples processus sont à l’œuvre, tels que le gestionnaire de réseau ou votre navigateur web.

Il est fascinant de constater que chaque action que nous effectuons sur notre ordinateur implique l’exécution d’un sous-processus. Cela reste vrai, même lorsque nous créons un simple script « hello world » en Python.

Le concept de sous-processus peut paraître complexe, même pour ceux qui sont familiers avec la programmation. Cet article se propose d’explorer en détail ce concept essentiel, ainsi que la manière d’utiliser la bibliothèque standard de sous-processus de Python.

À l’issue de ce guide, vous aurez acquis les compétences suivantes :

  • Compréhension approfondie du concept de sous-processus
  • Maîtrise des bases de la bibliothèque de sous-processus de Python
  • Mise en pratique de vos compétences Python grâce à des exemples concrets

Plongeons-nous dans le sujet.

Le principe du sous-processus

Un sous-processus est fondamentalement un processus informatique initié par un autre processus.

Imaginez les sous-processus comme un arbre généalogique, où chaque processus parent engendre des processus enfants qui s’exécutent sous sa supervision. Ce concept peut sembler abstrait, mais un simple schéma devrait éclairer le propos.

Il existe plusieurs outils pour visualiser les processus actifs sur votre ordinateur. Par exemple, sous UNIX (Linux et macOS), htop est un visualiseur de processus interactif très pratique.

Le mode arborescence est particulièrement utile pour examiner les relations entre les sous-processus. Il peut être activé en appuyant sur la touche F5.

En observant attentivement la section des commandes, vous pouvez discerner la structure hiérarchique des processus exécutés sur votre machine.

Tout commence avec /sbin/init, la commande qui initie tous les processus sur votre ordinateur. À partir de là, d’autres processus tels que xfce4-screenshoter et xfce4-terminal sont lancés, donnant naissance à d’autres sous-processus.

Sur Windows, le fameux Gestionnaire des tâches permet de surveiller les processus et de fermer ceux qui ne répondent plus.

Maintenant que le concept est clair, examinons comment implémenter des sous-processus en Python.

Sous-processus en Python

En Python, un sous-processus est une tâche qu’un script délègue au système d’exploitation.

La bibliothèque de sous-processus permet d’exécuter et de gérer ces sous-processus directement depuis Python, en interagissant avec les flux d’entrée standard (stdin), de sortie standard (stdout) et les codes de retour.

Il n’est pas nécessaire de l’installer via PIP, car elle est incluse dans la bibliothèque standard de Python.

Pour utiliser les sous-processus en Python, il suffit d’importer le module :

import subprocess

# Utilisation du module...

Notez que pour suivre ce tutoriel, vous devez disposer de Python 3.5 ou d’une version plus récente.

Pour vérifier la version de Python installée sur votre système, exécutez simplement :

❯ python --version
Python 3.9.5 # Mon résultat

Si vous obtenez une version 2.x de Python, vous pouvez utiliser la commande suivante :

python3 --version

L’idée centrale de la bibliothèque de sous-processus est de pouvoir interagir avec le système d’exploitation en exécutant n’importe quelle commande directement depuis l’interpréteur Python.

En d’autres termes, vous pouvez faire tout ce que votre système d’exploitation autorise, tant que vous ne supprimez pas votre système de fichiers racine 😅.

Voyons comment l’utiliser en créant un script qui liste les fichiers du répertoire courant.

Mise en œuvre du premier sous-processus

Commençons par créer un fichier nommé `list_dir.py`, qui servira de terrain de jeu pour notre expérience de listage de fichiers.

touch list_dir.py

Ouvrez ce fichier et insérez le code suivant :

import subprocess

subprocess.run('ls')

Nous importons d’abord le module `subprocess`, puis nous utilisons la fonction `run` pour exécuter la commande spécifiée en argument.

Cette fonction, introduite dans Python 3.5, est un raccourci pratique pour `subprocess.Popen`. Contrairement à `Popen`, où vous pouvez appeler la fonction `communicate` plus tard, `subprocess.run` exécute une commande et attend sa terminaison.

La commande `ls` est une commande UNIX qui affiche les fichiers du répertoire courant. Si vous exécutez ce code, vous obtiendrez donc la liste des fichiers présents dans le répertoire actuel.

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

Notez que sous Windows, vous devrez utiliser la commande `dir` à la place de `ls`.

Cela peut sembler trop simple, et vous avez raison. Pour exploiter pleinement la puissance du shell, apprenons à passer des arguments au shell avec `subprocess`.

Par exemple, pour afficher également les fichiers cachés (ceux qui commencent par un point) et toutes les métadonnées, vous pouvez écrire le code suivant :

import subprocess

# subprocess.run('ls') # Commande simple

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

Nous exécutons la commande sous forme de chaîne et utilisons l’argument `shell=True`. Cela signifie que nous invoquons un shell avant d’exécuter le sous-processus, et l’argument de la commande est interprété directement par le shell.

Cependant, l’utilisation de `shell=True` comporte de nombreux inconvénients, notamment des risques potentiels pour la sécurité. Vous pouvez en apprendre davantage à ce sujet dans la documentation officielle.

La meilleure façon de passer des commandes à la fonction `run` est d’utiliser une liste, où `lst[0]` est la commande à appeler ( `ls` dans ce cas) et `lst[n]` sont les arguments de cette commande.

En appliquant cette approche, notre code ressemblera à ceci :

import subprocess

# subprocess.run('ls') # Commande simple

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

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

Si vous souhaitez stocker la sortie standard d’un sous-processus dans une variable, vous pouvez le faire en définissant l’argument `capture_output` sur `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'

Pour accéder à la sortie d’un processus, nous utilisons l’attribut `stdout` de l’instance.

Dans ce cas, nous souhaitons stocker la sortie sous forme de chaîne de caractères au lieu d’octets. Pour cela, nous définissons l’argument `text` sur `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

Parfait ! Maintenant que nous maîtrisons les bases de la bibliothèque `subprocess`, passons à des exemples concrets d’utilisation.

Exemples d’utilisation de sous-processus en Python

Cette section présente quelques cas d’utilisation pratiques de la bibliothèque `subprocess`. Vous pouvez consulter tous les exemples sur ce référentiel Github.

Vérificateur de programme

L’une des principales utilisations de cette bibliothèque est la possibilité d’effectuer des actions simples sur le système d’exploitation.

Par exemple, un script simple qui vérifie si un programme est installé. Sous Linux, nous pouvons le faire avec la commande `which`.

'''Vérificateur de programme avec subprocess'''

import subprocess

program = 'git'

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

if process.returncode == 0:
    print(f'Le programme "{program}" est installé')

    print(f'L\'emplacement du binaire est : {process.stdout}')
else:
    print(f'Désolé, le programme {program} n\'est pas installé')

    print(process.stderr)

Notez que sous UNIX, lorsqu’une commande réussit, son code d’état est 0. Sinon, une erreur s’est produite lors de l’exécution.

Étant donné que nous n’utilisons pas l’argument `shell=True`, nous pouvons récupérer l’entrée de l’utilisateur en toute sécurité. De plus, nous pouvons vérifier si l’entrée est un programme valide avec un modèle d’expression régulière.

import subprocess

import re

programs = input('Séparez les programmes par un espace : ').split()

secure_pattern = 'mer'

for program in programs:

    if not re.match(secure_pattern, program):
        print("Désolé, nous ne pouvons pas vérifier ce programme")

        continue

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

    if process.returncode == 0:
        print(f'Le programme "{program}" est installé')

        print(f'L\'emplacement du binaire est : {process.stdout}')
    else:
        print(f'Désolé, le programme {program} n\'est pas installé')

        print(process.stderr)

    print('n')

Dans ce cas, nous récupérons les programmes entrés par l’utilisateur et nous utilisons une expression régulière pour nous assurer que la chaîne de programme ne contient que des lettres et des chiffres. Ensuite, nous vérifions l’existence de chaque programme avec une boucle `for`.

Grep simplifié en Python

Votre ami Tom a une liste de modèles dans un fichier texte et un autre grand fichier dans lequel il souhaite obtenir le nombre de correspondances pour chaque modèle. Il passait des heures à exécuter la commande grep pour chaque modèle.

Heureusement, vous savez comment résoudre ce problème avec Python, et vous l’aiderez à accomplir cette tâche en quelques secondes.

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'Le modèle "{pattern}" ne correspond à aucune ligne de {readfile}')

            continue

        print(f'Le modèle "{pattern}" a été trouvé {process.stdout.strip()} fois')

Dans ce script, nous définissons deux variables qui contiennent les noms des fichiers sur lesquels nous voulons travailler. Ensuite, nous ouvrons le fichier qui contient tous les modèles et nous le parcourons. Après cela, nous appelons un sous-processus qui exécute une commande grep avec l’option « -c » (qui signifie « compter ») et nous analysons la sortie pour déterminer les correspondances.

Si vous exécutez ce fichier (n’oubliez pas que vous pouvez télécharger les fichiers texte depuis le dépôt Github), vous obtiendrez le résultat attendu.

Configurer un virtualenv avec un sous-processus

L’une des choses les plus intéressantes que vous puissiez faire avec Python est l’automatisation de processus. Ce type de script peut vous faire gagner des heures chaque semaine.

Par exemple, nous allons créer un script d’installation qui crée un environnement virtuel pour nous et essaie de trouver un fichier `requirements.txt` dans le répertoire courant pour installer toutes les dépendances.

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('Désolé, python3 n\'est pas installé')

python_bin = process1.stdout.strip()

print(f'Python trouvé dans : {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'Votre environnement virtuel {VENV_NAME} a été créé')

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

if Path(REQUIREMENTS).exists():
    print(f'Fichier de requirements "{REQUIREMENTS}" trouvé')
    print('Installation des requirements')
    subprocess.run([pip_bin, 'install', '-r', REQUIREMENTS])

    print('Processus terminé ! Veuillez maintenant activer votre environnement avec "source .venv/bin/activate"')

else:
    print("Aucun requirement spécifié...")

Dans ce cas, nous utilisons plusieurs processus et analysons les données nécessaires dans notre script Python. Nous utilisons également la bibliothèque `pathlib`, qui permet de vérifier si le fichier `requirements.txt` existe.

Si vous exécutez le fichier Python, vous obtiendrez des messages informatifs sur ce qui se passe au niveau du système d’exploitation.

❯ python setup.py
Python trouvé dans : /usr/bin/python3
Votre environnement virtuel .venv a été créé
Fichier de requirements "requirements.txt" trouvé
Installation des requirements
Collecting asgiref==3.3.4 .......
Processus terminé ! Veuillez maintenant activer votre environnement avec "source .venv/bin/activate"

Notez que nous obtenons la sortie du processus d’installation, car nous ne redirigeons pas la sortie standard vers une variable.

Exécuter un autre langage de programmation

Nous pouvons exécuter d’autres langages de programmation avec Python et récupérer la sortie de ces fichiers. C’est possible, car les sous-processus interagissent directement avec le système d’exploitation.

Par exemple, créons un programme « hello world » en C++ et en Java. Pour exécuter le fichier suivant, vous devez installer les compilateurs C++ et Java.

helloworld.cpp

#include <iostream>

int main(){
    std::cout << "Ceci est un hello world en C++" << std::endl;
    return 0;
}

helloworld.java

class HelloWorld{
    public static void main(String args[]){
     System.out.println("Ceci est un hello world en Java");
    }
}

Je sais que cela semble beaucoup de code par rapport à un simple script Python d’une seule ligne, mais c’est uniquement à des fins de test.

Nous allons créer un script Python qui exécute tous les fichiers C++ et Java dans un répertoire. Pour ce faire, nous allons d’abord obtenir une liste de fichiers en fonction de l’extension de fichier, et la bibliothèque `glob` nous permet de le faire facilement !

from glob import glob

# Récupérer les fichiers de chaque extension
java_files = glob('*.java')

cpp_files = glob('*.cpp')

Après cela, nous pouvons commencer à utiliser des sous-processus pour exécuter chaque type de fichier.

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() + ' Au fait, ceci a été exécuté par 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 sous-processus Python a exécuté ceci :)'
        print(output)

Une petite astuce consiste à utiliser la méthode `strip` des chaînes de caractères pour modifier la sortie et obtenir uniquement ce dont nous avons besoin.

Attention : Soyez prudent lorsque vous exécutez des fichiers Java ou C++ volumineux, car nous chargeons leur sortie en mémoire, ce qui pourrait provoquer une fuite de mémoire.

Ouvrir des programmes externes

Nous pouvons exécuter d’autres programmes en appelant simplement leur emplacement binaire via un sous-processus.

Essayons-le en ouvrant Brave, mon navigateur web préféré.

import subprocess

subprocess.run('brave')

Cela ouvrira une instance du navigateur, ou simplement un nouvel onglet si le navigateur était déjà en cours d’exécution.

Comme pour tout programme qui accepte des options, nous pouvons les utiliser pour obtenir le comportement souhaité.

import subprocess

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

Pour résumer

Un sous-processus est un processus informatique créé par un autre processus. Nous pouvons vérifier les processus actifs sur notre ordinateur avec des outils tels que `htop` et le Gestionnaire des tâches.

Python dispose de sa propre bibliothèque pour travailler avec les sous-processus. Actuellement, la fonction `run` offre une interface simple pour créer et gérer des sous-processus.

Nous pouvons créer n’importe quel type d’application en utilisant des sous-processus, car nous interagissons directement avec le système d’exploitation.

Enfin, n’oubliez pas que la meilleure façon d’apprendre est de créer quelque chose qui vous serait utile.