Ce cours est visible gratuitement en ligne.

Paperback available in this course

Ce cours existe en eBook.

Certificate of achievement available at the end this course

Got it!
Apprenez à programmer en Python

Apprenez à programmer en Python

Last updated on Monday, September 8, 2014
  • 4 semaines
  • Facile

Un peu de programmation système

Dans ce chapitre, nous allons découvrir plusieurs modules et fonctionnalités utiles pour interagir avec le système. Python peut servir à créer bien des choses, des jeux, des interfaces, mais il peut aussi faire des scripts systèmes et, dans ce chapitre, nous allons voir comment.

Les concepts que je vais présenter ici risquent d'être plus familiers aux utilisateurs de Linux. Toutefois, pas de panique si vous êtes sur Windows : je vais prendre le temps de vous expliquer à chaque fois tout le nécessaire.

Les flux standard

Pour commencer, nous allons voir comment accéder aux flux standard (entrée standard et sortie standard) et de quelle façon nous devons les manipuler.

À quoi cela ressemble-t-il ?

Vous vous êtes sûrement habitués, quand vous utilisez la fonction print, à ce qu'un message s'affiche sur votre écran. Je pense que cela vous paraît même assez logique à présent.

Sauf que, comme pour la plupart de nos manipulations en informatique, le mécanisme qui se cache derrière nos fonctions est plus complexe et puissant qu'il y paraît. Sachez que vous pourriez très bien faire en sorte qu'en utilisant print, le texte s'écrive dans un fichier plutôt qu'à l'écran.

Quel intérêt ? print est fait pour afficher à l'écran non ?

Pas seulement, non. Mais nous verrons cela un peu plus loin. Pour l'instant, voilà ce que l'on peut dire : quand vous appelez la fonction print, si le message s'affiche à l'écran, c'est parce que la sortie standard de votre programme est redirigée vers votre écran.

On distingue trois flux standard :

  • L'entrée standard : elle est appelée quand vous utilisez input. C'est elle qui est utilisée pour demander des informations à l'utilisateur. Par défaut, l'entrée standard est votre clavier.

  • La sortie standard : comme on l'a vu, c'est elle qui est utilisée pour afficher des messages. Par défaut, elle redirige vers l'écran.

  • L'erreur standard : elle est notamment utilisée quand Python vous affiche le traceback d'une exception. Par défaut, elle redirige également vers votre écran.

Accéder aux flux standard

On peut accéder aux objets représentant ces flux standard grâce au module sys qui propose plusieurs fonctions et variables permettant d'interagir avec le système. Nous en reparlerons un peu plus loin dans ce chapitre, d'ailleurs.

>>> import sys
>>> sys.stdin # L'entrée standard (standard input)
<_io.TextIOWrapper name='<stdin>' encoding='cp850'>
>>> sys.stdout # La sortie standard (standard output)
<_io.TextIOWrapper name='<stdout>' encoding='cp850'>
>>> sys.stderr # L'erreur standard (standard error)
<_io.TextIOWrapper name='<stderr>' encoding='cp850'>
>>>

Ces objets ne vous rappellent rien ? Vraiment ?

Ils sont de la même classe que les fichiers ouverts grâce à la fonction open. Et il n'y a aucun hasard derrière cela.

En effet, pour lire ou écrire dans les flux standard, on utilise les méthodes read et write.

Naturellement, l'entrée standard stdin peut lire (méthode read) et les deux sorties stdout et stderr peuvent écrire (méthode write).

Essayons quelque chose :

>>> sys.stdout.write("un test")
un test7
>>>

Pas trop de surprise, sauf que ce serait mieux avec un saut de ligne à la fin. Là, ce que renvoie la méthode (le nombre de caractères écrits) est affiché juste après notre message.

>>> sys.stdout.write("Un test\n")
Un test
8
>>>

Modifier les flux standard

Vous pouvez modifier sys.stdin, sys.stdout et sys.stderr. Faisons un premier test :

>>> fichier = open('sortie.txt', 'w')
>>> sys.stdout = fichier
>>> print("Quelque chose...")
>>>

Ici, rien ne s'affiche à l'écran. En revanche, si vous ouvrez le fichier sortie.txt, vous verrez le message que vous avez passé à print.

Je ne trouve pas le fichier sortie.txt, où est-il ?

Il doit se trouver dans le répertoire courant de Python. Pour connaître l'emplacement de ce répertoire, utilisez le module os et la fonction getcwd (Get Current Working Directory).

Une petite subtilité : si vous essayez de faire appel à getcwd directement, le résultat ne va pas s'afficher à l'écran… il va être écrit dans le fichier. Pour rétablir l'ancienne sortie standard, tapez la ligne :

sys.stdout = sys.__stdout__

Vous pouvez ensuite faire appel à la fonction getcwd :

import os
os.getcwd()

Dans ce répertoire, vous devriez trouver votre fichier sortie.txt.

Si vous avez modifié les flux standard et que vous cherchez les objets d'origine, ceux redirigeant vers le clavier (pour l'entrée) et vers l'écran (pour les sorties), vous pouvez les trouver dans sys.__stdin__, sys.__stdout__ et sys.__stderr__.

La documentation de Python nous conseille malgré tout de garder de préférence les objets d'origine sous la main plutôt que d'aller les chercher dans sys.__stdin__, sys.__stdout__ et sys.__stderr__.

Voilà qui conclut notre bref aperçu des flux standard. Là encore, si vous ne voyez pas d'application pratique à ce que je viens de vous montrer, cela viendra certainement par la suite.

Les signaux

Les signaux sont un des moyens dont dispose le système pour communiquer avec votre programme. Typiquement, si le système doit arrêter votre programme, il va lui envoyer un signal.

Les signaux peuvent être interceptés dans votre programme. Cela vous permet de déclencher une certaine action si le programme doit se fermer (enregistrer des objets dans des fichiers, fermer les connexions réseau établies avec des clients éventuels, …).

Les signaux sont également utilisés pour faire communiquer des programmes entre eux. Si votre programme est décomposé en plusieurs programmes s'exécutant indépendamment les uns des autres, cela permet de les synchroniser à certains moments clés. Nous ne verrons pas cette dernière fonctionnalité ici, elle mériterait un cours à elle seule tant il y aurait de choses à dire !

Les différents signaux

Le système dispose de plusieurs signaux génériques qu'il peut envoyer aux programmes quand cela est nécessaire. Si vous demandez l'arrêt du programme, un signal particulier lui sera envoyé.

Tous les signaux ne se retrouvent pas sur tous les systèmes d'exploitation, c'est pourquoi je vais surtout m'attacher à un signal : le signal SIGINT envoyé à l'arrêt du programme.

Pour plus d'informations, un petit détour par la documentation s'impose, notamment du côté du module signal.

Intercepter un signal

Commencez par importer le module signal.

import signal

Le signal qui nous intéresse, comme je l'ai dit, se nomme SIGINT.

>>> signal.SIGINT
2
>>>

Pour intercepter ce signal, il va falloir créer une fonction qui sera appelée si le signal est envoyé. Cette fonction prend deux paramètres :

  • le signal (plusieurs signaux peuvent être envoyés à la même fonction) ;

  • le frame qui ne nous intéresse pas ici.

Cette fonction, c'est à vous de la créer. Ensuite, il faudra la connecter avec le signal SIGINT.

D'abord, créons notre fonction :

import sys

def fermer_programme(signal, frame):
    """Fonction appelée quand vient l'heure de fermer notre programme"""
    print("C'est l'heure de la fermeture !")
    sys.exit(0)

C'est quoi, la dernière ligne ?

On demande simplement à notre programme Python de se fermer. C'est le comportement standard quand on réceptionne un tel signal et notre programme doit bien s'arrêter à un moment ou à un autre.

Pour ce faire, on utilise la fonction exit (sortir, en anglais) du module sys. Elle prend en paramètre le code de retour du programme.

Pour simplifier, la plupart du temps, si votre programme renvoie 0, le système comprendra que tout s'est bien passé. Si c'est un entier autre que 0, le système interprétera cela comme une erreur ayant eu lieu pendant l'exécution de votre programme.

Ici, notre programme s'arrête normalement, on passe donc à exit0.

Connectons à présent notre fonction au signal SIGINT, sans quoi notre fonction ne serait jamais appelée.

On utilise pour cela la fonction signal. Elle prend en paramètre :

  • le signal à intercepter ;

  • la fonction que l'on doit connecter à ce signal.

signal.signal(signal.SIGINT, fermer_programme)

Ne mettez pas les parenthèses à la fin du nom de la fonction. On envoie la référence vers la fonction, on ne l'exécute pas.

Cette ligne va connecter le signal SIGINT à la fonction fermer_programme que vous avez définie plus haut. Dès que le système enverra ce signal pour fermer le programme, la fonction fermer_programme sera appelée.

Pour vérifier que tout fonctionne bien, lancez une boucle infinie dans votre programme :

print("Le programme va boucler...")
while True: # Boucle infinie, True est toujours vrai
    continue

Je vous remets le code en entier, si cela vous rend les choses plus claires :

import signal
import sys

def fermer_programme(signal, frame):
    """Fonction appelée quand vient l'heure de fermer notre programme"""
    print("C'est l'heure de la fermeture !")
    sys.exit(0)

# Connexion du signal à notre fonction
signal.signal(signal.SIGINT, fermer_programme)

# Notre programme...
print("Le programme va boucler...")
while True:
    continue

Quand vous lancez ce programme, vous voyez un message vous informant que le programme va boucler… et le programme continue de tourner. Il ne s'arrête pas. Il ne fait rien, il boucle simplement mais il va continuer de boucler tant que son exécution n'est pas interrompue.

Dans la fenêtre du programme, tapez CTRL + C sur Windows ou Linux, Cmd + C sur Mac OS X.

Cette combinaison de touches va demander au programme de s'arrêter. Après l'avoir saisie, vous pouvez constater qu'effectivement, votre fonction fermer_programme est bien appelée et s'occupe de fermer le programme correctement.

Voilà pour les signaux. Si vous voulez aller plus loin, rendez-vous sur la documentation du module signal.

Interpréter les arguments de la ligne de commande

Python nous offre plusieurs moyens, en fonction de nos besoins, pour interpréter les arguments de la ligne de commande. Pour faire court, ces arguments peuvent être des paramètres que vous passez au lancement de votre programme et qui influeront sur son exécution.

Ceux qui travaillent sur Linux n'auront, je pense, aucun mal à me suivre. Mais je vais faire une petite présentation pour ceux qui viennent de Windows, afin qu'ils puissent suivre sans difficulté.

Si vous êtes allergiques à la console, passez à la suite.

Accéder à la console de Windows

Il existe plusieurs moyens d'accéder à la console de Windows. Celui que j'utilise et que je vais vous montrer passe par le Menu Démarrer.

Ouvrez le Menu Démarrer et cliquez sur exécuter…. Dans la fenêtre qui s'ouvre, tapez cmd puis appuyez sur Entrée.

Vous devriez vous retrouver dans une fenêtre en console, vous donnant plusieurs informations propres au système.

C:\WINDOWS\system32\cmd.exe
Microsoft Windows XP [version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
C:\Documents and Settings\utilisateur>

Ce qui nous intéresse, c'est la dernière ligne. C'est un chemin qui vous indique à quel endroit de l'arborescence vous vous trouvez. Il y a toutes les chances que ce chemin soit le répertoire utilisateur de votre compte.

C:\Documents and Settings\utilisateur>

Nous allons commencer par nous déplacer dans le répertoire contenant l'interpréteur Python. Là encore, si vous n'avez rien changé lors de l'installation de Python, le chemin correspondant est C:\pythonXY, XY représentant les deux premiers chiffres de votre version de Python. Avec Python 3.4, ce sera donc probablement C:\python34.

Déplacez-vous dans ce répertoire grâce à la commande cd.

C:\Documents and Settings\utilisateur>cd C:\python34
C:\Python34>

Si tout se passe bien, la dernière ligne vous indique que vous êtes bien dans le répertoire Python.

En vérité, vous pouvez appeler Python de n'importe où dans l'arborescence mais ce sera plus simple si nous sommes dans le répertoire de Python pour commencer.

Accéder aux arguments de la ligne de commande

Nous allons une fois encore faire appel à notre module sys. Cette fois, nous allons nous intéresser à sa variable argv.

Créez un nouveau fichier Python. Sur Windows, prenez bien soin de l'enregistrer dans le répertoire de Python (C:\python34 sous Python 3.4).

Placez-y le code suivant :

import sys
print(sys.argv)

sys.argv contient une liste des arguments que vous passez en ligne de commande, au moment de lancer le programme. Essayez donc d'appeler votre programme depuis la ligne de commande en lui passant des arguments.

Sur Windows :

C:\Python34>python test_console.py
['test_console.py']
C:\Python34>python test_console.py arguments
['test_console.py', 'arguments']
C:\Python34>python test_console.py argument1 argument2 argument3
['test_console.py', 'argument1', 'argument2', 'argument3']
C:\Python34>

Comme vous le voyez, le premier élément de sys.argv contient le nom du programme, de la façon dont vous l'avez appelé. Le reste de la liste contient vos arguments (s'il y en a).

Note : vous pouvez très bien avoir des arguments contenant des espaces. Dans ce cas, vous devez alors encadrer l'argument de guillemets :

C:\Python34>python test_console.py "un argument avec des espaces"
['test_console.py', 'un argument avec des espaces']
C:\Python34>

Interpréter les arguments de la ligne de commande

Accéder aux arguments, c'est bien, mais les interpréter peut être utile aussi.

Des actions simples

Parfois, votre programme devra déclencher plusieurs actions en fonction du premier paramètre fourni. Par exemple, en premier argument, vous pourriez préciser l'une des valeurs suivantes : start pour démarrer une opération, stop pour l'arrêter, restart pour la redémarrer, status pour connaître son état… bref, les utilisateurs de Linux ont sûrement bien plus d'exemples à l'esprit.

Dans ce cas de figure, il n'est pas vraiment nécessaire d'interpréter les arguments de la ligne de commande, comme on va le voir. Notre programme Python ressemblerait simplement à cela :

import sys

if len(sys.argv) < 2:
    print("Précisez une action en paramètre")
    sys.exit(1)

action = sys.argv[1]

if action == "start":
    print("On démarre l'opération")
elif action == "stop":
    print("On arrête l'opération")
elif action == "restart":
    print("On redémarre l'opération")
elif action == "status":
    print("On affiche l'état (démarré ou arrêté ?) de l'opération")
else:
    print("Je ne connais pas cette action")
Des options plus complexes

Mais la ligne de commande permet également de transmettre des arguments plus complexes comme des options. La plupart du temps, nos options sont sous la forme : -option_courte (une seule lettre), --option_longue, suivie d'un argument ou non.

Souvent, une option courte est accessible aussi depuis une option longue.

Ici, mon exemple va être tiré de Linux, mais vous n'avez pas vraiment besoin d'être sur Linux pour le comprendre, rassurez-vous.

La commande ls permet d'afficher le contenu d'un répertoire. On peut lui passer en paramètres plusieurs options qui influent sur ce que la commande va afficher au final.

Par exemple, pour afficher tous les fichiers (cachés ou non) du répertoire, on utilise l'option courte a.

$ ls -a                                                    
.  ..  fichier1.txt  .fichier_cache.txt  image.png                              
$

Cette option courte est accessible depuis une option longue, all. Vous arrivez donc au même résultat en tapant :

$ ls --all                                                 
.  ..  fichier1.txt  .fichier_cache.txt  image.png                              
$

Pour récapituler, nos options courtes sont précédées d'un seul tiret et composées d'une seule lettre. Les options longues sont précédées de deux tirets et composées de plusieurs lettres.

Certaines options attendent un argument, à préciser juste après l'option.

Par exemple (toujours sur Linux), pour afficher les premières lignes d'un fichier, vous pouvez utiliser la commande head. Si vous voulez afficher les X premières lignes d'un fichier, vous utiliserez la commande head -n X.

$ head -n 5 fichier.txt
ligne 1
ligne 2
ligne 3
ligne 4
ligne 5
$

Dans ce cas, l'option -n attend un argument qui est le nombre de lignes à afficher.

Interpréter ces options grâce à Python

Cette petite présentation faite, revenons à Python.

Nous allons nous intéresser au module argparse qui est utile, justement, pour interpréter les arguments de la ligne de commande selon un certain schéma. La base du code est la suivante :

import argparse
parser = argparse.ArgumentParser()
parser.parse_args()
  1. D'abord, on importe le module argparse ;

  2. on crée ensuite un argparse.ArgumentParser qui va être utile pour configurer nos options à interpréter ;

  3. enfin, on appelle la méthode parse_args() sur notre parser. Cette méthode retourne les arguments interprétés. Nous allons voir comment préciser des options dans notre parser, pour rendre les choses plus intéressantes. Notez que, par défaut, l'interprétation des arguments se fait depuis sys.argv[1:] (c'est-à-dire la liste des arguments sans le nom du script).

En fait, notre parser n'est pas tout à fait vide. Si vous exécutez le script ci-dessus avec l'option --help :

>python code.py --help
usage: code.py [-h]

optional arguments:
  -h, --help  show this help message and exit

>

Ce qui vous donne un petit aperçu de comment utiliser notre programme. L'aide (option -h ou --help) est générée par défaut. Et si vous n'utilisez pas le script convenablement :

>python code.py --inexistante
usage: code.py [-h]
code.py: error: unrecognized arguments: --inexistante

>

Les messages d'erreurs sont en anglais, mais vous devriez pouvoir comprendre l'erreur. Ici nous avons simplement spécifié une option qui n'a pas été définie. Essayons d'en définir une :

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("x", help="le nombre a mettre au carré")
parser.parse_args()

Nous avons ajouté une option grâce à la méthode add_argument(). Elle prend plusieurs paramètres (de nombreux paramètres optionnels, en fait) mais nous n'en avons précisé que deux ici : l'option et le message d'aide lié.

Si vous demandez l'aide du script :

>python code.py --help
usage: code.py [-h] x

positional arguments:
  x           le nombre à mettre au carré

optional arguments:
  -h, --help  show this help message and exit

>

Nous devons maintenant préciser un nombre x en paramètre. Essayons de récupérer sa valeur :

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("x", help="le nombre a mettre au carré")
args = parser.parse_args()
print("Vous avez précisé X =", args.x)

Pour récupérer les options (ce que nous voudrons faire la plupart du temps ;) ), on récupère le retour de la méthode parse_args(). Elle retourne un objet namespace avec nos options en attribut. Accéder à args.x retourne donc le nombre précisé par l'utilisateur :

>python code.py 5
Vous avez précisé X = 5

>

Dans ce contexte, on veut un nombre... mais l'utilisateur peut entrer n'importe quoi. Ce n'est pas une bonne chose, modifions notre méthode add_argument pour que l'utilisateur ne puisse entrer que des nombres :

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("x", type=int, help="le nombre à mettre au carré")
args = parser.parse_args()
x = args.x
retour = x ** 2
print(retour)

Comme vous le voyez, la méthode add_argument est précisée ici avec un nouvel argument : type. On lui précise int, ce qui veut dire que l'on attend un nombre (l'entrée de l'utilisateur sera automatiquement convertie).

Vous pouvez voir aussi que notre programme fait maintenant quelque chose de concret :

>python code.py 5
25

>python code.py -8
64

>python code.py test
usage: code.py [-h] x
code.py: error: argument x: invalid int value: 'test'

>

Comme vous le voyez, la conversion marche bien, jusqu'au message d'erreur affiché si l'utilisateur n'entre pas un nombre.

Jusqu'ici nous avons créé des "positional arguments", qui doivent être précisés sans option. Voyons comment ajouter des options facultatives :

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("x", type=int, help="le nombre à mettre au carré")
parser.add_argument("-v", "--verbose", action="store_true",
        help="augmente la verbosité")
args = parser.parse_args()

x = args.x
retour = x ** 2
if args.verbose:
    print("{} ^ 2 = {}".format(x, retour))
else:
    print(retour)

Nous avons ajouté une nouvelle option : -v ou --verbose. Le nom commençant par un tiret, argparse suppose qu'il s'agit d'une option facultative, même si cela peut être modifié.

Notez que l'on appelle la méthode add_argument avec l'argument action. L'action précisée, "store_true", permet de convertir l'option précisée en booléen :

  • Si l'option est précisée, alors args.verbose vaudra True ;

  • si l'option n'est pas précisée, alors args.verbose vaudra False.

Le résultat affiché est différent en fonction de l'option, si elle est précisée, le message de retour est un peu plus détaillé :

<code type=console">
>python code.py -h
usage: code.py [-h] [-v] x

positional arguments:
x le nombre à mettre au carré

optional arguments:
-h, --help show this help message and exit
-v, --verbose augmente la verbosité

>python code.py 5
25

>python code.py 5 --verbose
5 ^ 2 = 25

>python code.py -v 5
5 ^ 2 = 25

>
</code>

Vous voyez que le retour est différent en fonction du niveau de verbosité. Notez aussi que le message d'aide intègre bien notre nouvelle option. C'est l'une des raisons (il y en a beaucoup) qui rendent l'utilisation de argparse si pratique.

Nous n'avons vu que le tout début des fonctionnalités de ce module. Si vous voulez en apprendre plus, les ressources suivantes vont bien plus loin :

Exécuter une commande système depuis Python

Nous allons ici nous intéresser à la façon d'exécuter des commandes depuis Python. Nous allons voir deux moyens, il en existe cependant d'autres.

Ceux que je vais présenter ont l'avantage de fonctionner sur Windows.

La fonction system

Vous vous souvenez peut-être de cette fonction du module os. Elle prend en paramètre une commande à exécuter, affiche le résultat de la commande et renvoie son code de retour.

os.system("ls") # Sur Linux
os.system("dir") # Sur Windows

Vous pouvez capturer le code de retour de la commande mais vous ne pouvez pas capturer le retour affiché par la commande.

En outre, la fonction system exécute un environnement particulier rien que pour votre commande. Cela veut dire, entre autres, que system retournera tout de suite même si la commande tourne toujours.

En gros, si vous faites os.system(« sleep 5 »), le programme ne s'arrêtera pas pendant cinq secondes.

La fonction popen

Cette fonction se trouve également dans le module os. Elle prend également en paramètre une commande.

Toutefois, au lieu de renvoyer le code de retour de la commande, elle renvoie un objet, un pipe (mot anglais pour un « tuyau ») qui vous permet de lire le retour de la commande.

Un exemple sur Linux :

>>> import os
>>> cmd = os.popen("ls")                                                        
>>> cmd                                                                         
<os._wrap_close object at 0x7f81d16554d0>                                       
>>> cmd.read()                                                                  
'fichier1.txt\nimage.png\n'                                                     
>>>

Le fait de lire le pipe bloque le programme jusqu'à ce que la commande ait fini de s'exécuter.

Je vous ai dit qu'il existait d'autres moyens. Et au-delà de cela, vous avez beaucoup d'autres choses intéressantes dans le module os vous permettant d'interagir avec le système… et pour cause !

En résumé

  • Le module sys propose trois objets permettant d'accéder aux flux standard : stdin, stdout et stderr.

  • Le module signal permet d'intercepter les signaux envoyés à notre programme.

  • Le module argparse permet d'interpréter les arguments passés en console à notre programme.

  • Enfin, le module os possède, entre autres, plusieurs fonctions pour envoyer des commandes au système.

Example of certificate of achievement
Example of certificate of achievement