Arguments de ligne de commandes avec argparse et Python
Préambule
Dans ce tutoriel, nous allons voir comment gérer les arguments sur la ligne de commande d'un programme en Python, avec la librairie argparse.
L'outil, nous fournis tout un panel de fonctionnalités, contrairement à l'habituel getopt, qui lui se contente juste d'obtenir les arguments et rien d'autres. argparse lui, gère automatiquement l'aide, les arguments longs (--) et pleins d'autres choses, afin de gérer plus efficacement.
La base
Le système de base est le suivant :
import argparse
parser = argparse.ArgumentParser()
parser.parse_args()
La première ligne, consiste à importer la librairie qui est fournie en standard avec toute installation de Python. Ensuite, on charge la classe ArgumentParser, et enfin on lui demande de gérer les arguments.
En retour, si on appel le programme avec l'argument -h, il va automatiquement nous afficher l'aide. C'est déjà un premier pas de fait : pas besoin comme avec getopt, de gérer cela, il le fait en automatique.
ArgumentParser
Nous avons vu qu'il nous fallait créer une argument de ArgumentParser, ce dernier peut accueillir plusieurs paramètres. Chaque paramètre, permet de donner une information au sujet du programme et au niveau de l'aide.
prog
L'argument prog, permet de définir le nom du programme, par défaut c'est le nom du script python donc si le nom du script change, le nom changera.
description
Permet d'indiquer la description du programme, expliquer ce que fait le programme en quelques mots. Ce texte est affiché quand on demande l'affichage de l'aide (-h ou --help).
epilog
Comme la description, mais après l'aide. Ce sera plutôt une information du style un lien vers la documentation complète par exemple.
formatter_class
Permet d'indiquer une classe qui va servir à personnaliser la sortie du message d'aide. Par défaut c'est : argoarse.HelpFormatter.
prefix_chars
Permet de définir le paramètre qui précède un argument optionnel de ligne de commande, par défaut c'est le tiret (ou le double-tiret pour les arguments longs). On peut le changer, mais ça serait aller à l'encontre du fonctionnement habituel. Cela dit, sous Windows c'est une slash (/).
add_help
Permet d'indiquer qu'on souhaite ou non, disposer de l'option d'aide. Par défaut oui, cela permet donc dès le départ de disposer de -h et --help pour afficher l'aide.
exit_on_error
Détermine si l'exécution s'arrête avec un message d'erreur en cas de problème, par défaut oui.
usage
Permet de modifier la sortie de la ligne usage, qui permet d'indiquer globalement comment se servir du programme.
add_argument
La method, permet d'ajouter un nouvel argument que le système doit gérer. Il y a plusieurs types différents : arguments positionnels, arguments optionnel et arguments requis.
argument positionnel
L'argument positionnel, correspond à la commande du programme. Par exemple mon script de gestion MySQL réalisé en bash il y a quelques temps, dispose entre autres d'un argument cuser qui permet d'indiquer qu'on veut créer un utilisateur MYSQL. En Python, c'est ce qu'on appel un argument positionnel.
argument requis
Ce sont tout les arguments à coups de - ou --, mais dont une valeur doit être obligatoirement fournis.
argument optionnel
Comme l'argument requis, mais qui accepte qu'on ne passe pas de valeurs, comme -h par exemple.
name_or_flags
Le paramètre peut-être utilisé plusieurs fois (pas besoin donc de nommer le paramètre), il permet d'indiquer une liste d'arguments, quel que soit le type. Un élément est obligatoire, mais on peut en avoir plusieurs. Par exemple -h et --help, sont deux éléments name_or_flags.
action
Le paramètre permet d'indiquer l'action à entreprendre :
- store : stocke la valeur et c'est tout
- store_const : stocke la valeur qui est passée à l'argument const. C'est très utile pour des arguments qui sont optionnels, cela permet de spécifié une valeur (qui ne bougera pas).
- append : stocke une liste et ajoute la valeur à la liste. Utile pour les options qu'on peut spécifier plusieurs fois dans la liste de commande.
- count : compte le nombre d'occurrences de l'argument. On s'en sert souvent pour le niveau de verbosité (-v, -vv, -vvv).
help
C'est le texte qui s'affichera sur la page d'aide, relativement à la commande concernée.
nargs
Permet de spécifier le nombre d'arguments capturés ensemble. Par exemple la valeur deux, indiquera qu'il faut deux éléments. On peut aussi spécifier un point d'interrogation qui indiquera que c'est optionnel (0 ou 1) et plus qui indique 1 ou +. Avec l'étoile en revanche, c'est l'ensemble des arguments qui sont rassemblés dans une liste.
default
Permet d'indiquer une valeur par défaut pour les arguments optionnels et certains arguments positionnels.
type
Permet d'indiquer le type de valeur attendu pour l'argument.
choices
Permet de définir un ensemble de valeurs structe, l'utilisateur doit alors choisir parmi cette liste.
required
Permet de définir que le paramètre est optionnel (False) ou requis (True). Par défaut il est optionnel.
parse_args
Cette méthode permet de confertir les arguments en objets afin de les assigner comme attributs de l'objet namespace et renvoi ce dernier une fois remplis. Par exemple :
args = parser.parse_args()
args
Utilisé plutôt pour simuler l'appel du programme avec des arguments. Par défaut, c'est sys.argv qui est utilisé, c'est à dire ce qu'on fournis dans la ligne de commande.
namespace
Permet de fournir une instance de Namespace, par défaut c'est une nouvelle instance qui est créée.
add_subparsers
Permet de définir des sous-commandes, c'est à dire des commandes comme celles du programme principale, mais qui concernes d'autres parties. Par exemple git add, le add est une sous-commande, alors que git init c'est une autre sous-commande (cette fois init).
Au final, c'est comme si on avait deux programmes et donc deux parser ; en pratique on a en effet plusieurs parser, mais au sein d'un seul et même programme. Toutefois, les commandes sont séparées les unes des autres. On peut donc avoir sur une sous-commande, des arguments qui lui sont propre et qui peuvent tout à fait être identique d'une sous-commande à l'autre.
parse_known_args
Comme parse_args, mais ici ne renvoi pas d'erreurs si l'argument n'est pas reconnu. Pour donner un exemple, la commande ls de Linux qui permet de lister les fichiers, accepte qu'il y ai des arguments non gérés par l'analyseur. Si on tape la commande suivante :
ls /etc
La commande va alors lister tout les fichiers, dossiers et liens symboliques du dossier /etc. L'argument qu'on a fournis, est un argument qui ne doit pas être analysé par l'analyseur. Ainsi, si on souhaite reproduire ce fonctionnement, nous utiliserons parse_know_args, qui va alors nous renvoyer cet argument dans le namespace en tan que paire de valeurs.
L'instance de Namespace, ici que nous allons appeler args, contient donc deux choses :
- Une propriété par arguments connus avec sa valeur concernée (si valeur)
- Un tuple d'arguments non connus.
Pour la propriété, c'est args.property tout simplement ; la liste est aussi obtenue avec : args[0] (retour de l'instance de la classe). En revanche, pour obtenir la liste d'arguments données qui ne sont pas connus, on utilisera : args[1], pour récupérer un tuple avec la liste des arguments comme valeurs. Attention cependant, un argument avec un égal comme on le voit souvent sur les arguments longs (--) pour définir la valeur, correspondra à un seul et unique argument non connu. C'est le parsing par l'analyseur, qui permet de séparer l'argument de sa valeur, ici comme l'argument est inconnu, il n'est pas parsé donc c'est une simple chaîne et rien d'autres.
Conclusion
Nous avons vu comment nous pouvons gérer les arguments de ligne de commande. Tout ce que nous tapons à la suite du nom du programme, est un argument. Ainsi, nous avons l'argument qui est une simple commande (init de git init par exemple), appelé ici un argument positionnel et l'argument avec - ou -- (argument court et argument long).
Dès que nous souhaitons pouvoir utiliser des arguments inconnus, il nous faut utiliser parse_known_args, sinon nous aurons une erreur. Dans les programmes comme git par exemple, nous avons toujours un argument connu, on peut donc considérer que l'ensemble des arguments fournis derrière, sont des valeurs de l'argument ; dans ce cas là, aucun soucis. En revanche pour les programmes comme ls ou mkdir par exemple, nous avons des arguments inconnus qui sont souvent des noms de fichiers, de dossiers ou de liens symbolique ; on ne peut donc pas prévoir cela à l'avance. Il est alors nécessaire, de permettre l'usage d'arguments inconnus sans renvoyer d'erreurs, afin de les traiter par la suite.
Astuces de gestion
Une astuce de gestion des arguments, est d'utiliser un conditionnement if sur les propriétés de args (ou un switch si on préfère). En effet, dès qu'un argument est fournis, il est dans cette variable (une instance de Namespace) ; donc en testant son existence, on peut réagir à sa présence.
Si on prends l'exemple d'un outil console pour générer du code par exemple, il va être nécessaire de poser un ensemble de questions à l'utilisateur. On peut aussi lui permettre de pré-répondre à certaines via les arguments (par exemple un pour indiquer qu'on ne souhaite pas la demande de validation et qu'on est d'accord dans tout les cas). Avec donc le conditionnement, on peut regarder si l'argument est présent, si ce n'est pas le cas alors on pose la question sinon on la zappe puisque nous avons déjà la réponse.