Python, parsing des arguments avec les packages argparse and getopt

Logo

Introduction

Les fonctionnalités essentielles à aborder lorsqu’on souhaite apprendre et utiliser très rapidement Python :

Dans ce chapître, comment gérer les arguments d’un programme Python.

On veut créer le programme googleindex.py qui récupère le statut de l’indexation pour une URL donnée, programme avec 2 arguments :

python3 googleindex.py --address <url>  [, --jsonauth <path to json auth file> ]

Le premier argument --address est obligatoire, le second --jsonauth est optionnel.

Les options courtes doivent être disponibles :

python3 googleindex.py -a <url>  [, -j <path to json auth file> ]

Le tableau sys.argv

Le tableau système sys.argv stocke les informations sur les arguments : l’indice 0 contient le nom du script, tous les autres indices les arguments.

import sys

print("Script name : %s" % (sys.argv[0]))
print("First argument : %s" % (sys.argv[1]))
print("All arguments : %s" % (sys.argv[1:]))
python3 googleindex.py test.html google.json
Script name : googleindex.py
First argument : test.html
All arguments : ['test.html', 'google.json']

sys.argv est une variable publique globale.

Un argument peut être vérifié avec un bloc try :

import sys

try:
	arg = sys.argv[2]
except IndexError:
	raise SystemExit("Usage : %s url json" % (sys.argv[0]))

Utiliser sys.argv est simple, mais nous souhaitons la syntaxe usuelle

python3 googleindex.py --address test.html --jsonauth google.json
python3 googleindex.py -a test.html -j google.json

Avec sys.argv, ça devient alors un peu plus compliqué, chaque chaîne devenant en effet un argument :

Script name : googleindex.py
First argument : --address
All arguments : ['--address', 'test.html', '--jsonauth', 'google.json']

2 librairies existent pour gérer ces lignes de commandes plus sophistiquées :

  • argparse
  • getopt

Pas besoin d’installation, ces 2 packages sont dans le cœur du moteur Python.

argparse

Le package argparse est importé et un objet argparse.ArgumentParser() est instancié.

import argparse
parser = argparse.ArgumentParser()

Le nom du programme est disponible dans la propriété parser.prog.

import argparse
parser = argparse.ArgumentParser()
print(parser.prog)
googleindex.py

Les arguments sont ajoutés avec la méthode add_argument, très facile à utiliser :

import argparse

parser = argparse.ArgumentParser()

parser.add_argument("--address", help="URL to be checked", required=True)
parser.add_argument("--jsonauth", help="JSON Google Authentication file path")
parser.add_argument("--verbosity", help="Verbosity", action="store_false")
  • pour spécifier un argument obligatoire : required=True
  • pour appliquer une constante booléenne lorsque l’argument n’est pas spécifié : action="store_true | store_false". Dans l’exemple ci-dessus, la verbosité sera à False si --verbosity n’est pas donné, sinon True.

Une valeur par défaut peut être définie pour les paramètres optionnels :

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--address", help="URL to be checked", required=True)
parser.add_argument("--jsonauth",
                      default="/home/sqlpac/google-auth.json",
                      help="JSON Google Authentication file path")
parser.add_argument("--verbosity", help="Verbosity", action="store_false")

Voyons un premier résultat :

python3 googleindex.py
usage: learn-argparse.py [-h] --address ADDRESS [--jsonauth JSONAUTH] [--verbosity]
googleindex.py: error: the following arguments are required: --address

L’option --help est immédiatement prête à l’emploi :

python3 googleindex.py --help
usage: googleindex.py [-h] --address ADDRESS [--jsonauth JSONAUTH] [--verbosity]

optional arguments:
  -h, --help           show this help message and exit
  --address ADDRESS    URL to be indexed
  --jsonauth JSONAUTH  JSON Google Authentication file path, default $HOME/google-auth.json
  --verbosity          Verbosity

Utiliser la méthode parser_args pour récupérer les valeurs des arguments, l’objet résultant est un espace de nom (namespace) et chaque propriété est un argument :

args = parser.parse_args()
print(args)
print(args.address)
print(args.jsonauth)
python3 googleindex.py --address 1.html --jsonauth google.json
Namespace(address='1.html', jsonauth='google.json', verbosity=False)
1.html
google.json

Le nom de la propriété est modifiable avec dest

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--address", help="URL to be checked", required=True)
parser.add_argument("--jsonauth",
                      default="/home/sqlpac/google-auth.json",
                      dest="jfile",
                      help="JSON Google Authentication file path")
parser.add_argument("--verbosity", help="Verbosity", action="store_false")

print(args)
print(args.jfile)
python3 googleindex.py --address 1.html --jsonauth google.json
Namespace(address='1.html', jfile='google.json', verbosity=False)
google.json

Par défaut, le type de données des propriétés est un string. Pour forcer un type de données, utiliser type=<datatype> lors de la définition de l’argument, cela renforce automatiquement les règles de validation des types de données des arguments :

import argparse

parser = argparse.ArgumentParser()
…
parser.add_argument("--year",
                      type=int,
                      default=2020, help="Year extraction")
                                            
…
usage: googleindex.py.py [-h] --address ADDRESS [--jsonauth JFILE] [--verbosity] [--year YEAR]
googleindex.py.py: error: argument --year: invalid int value: 'onestring'

Pour combiner les options courtes et longues (-a, --address), utiliser simplement add_argument('short-option','long-option',…)

import argparse

parser = argparse.ArgumentParser()

parser.add_argument("-a","--address", help="URL to be indexed", required=True)
…

Tout est prêt en très peu de lignes de code :

import argparse

parser = argparse.ArgumentParser()

parser.add_argument("-a","--address", help="URL to be indexed", required=True)
parser.add_argument("-j","--jsonauth",
					help="JSON Google Authentication file path, default $HOME/google-auth.json",
					default="/home/sqlpac/google-auth.json",
					dest="jfile")
parser.add_argument("-y","--year",
                      type=int,
                      default=2020,
                      help="Year extraction")
parser.add_argument("-v","--verbosity", help="Verbosity", action="store_false")

args = parser.parse_args()

if args.year :
  print('Current year selected')
…
python3 googleindex.py --address 1.html --jsonauth google.json --year 2020
Current year selected
python3 googleindex.py --help
usage: googleindex.py [-h] -a ADDRESS [-j JFILE] [-y YEAR] [-v]

optional arguments:
  -h, --help            show this help message and exit
  -a ADDRESS, --address ADDRESS
                        URL to be indexed
  -j JFILE, --jsonauth JFILE
                        JSON Google Authentication file path, default $HOME/google-auth.json
  -y YEAR, --year YEAR  Year extraction
  -v, --verbosity       Verbosity

Option nargs

Parfois on a besoin de pouvoir donner de multiples valeurs à un argument, par exemple :

python3 googleindex.py --address url1.html url2.html 

Cela peut être réalisé avec l’option nargs='*' :

import argparse

parser = argparse.ArgumentParser()

parser.add_argument('-a','--address', nargs='*', help='URLs to be indexed', required=True)
…

args = parser.parse_args()

print(args.address)
print(type(args.address))

Dès lors le type de données de l’argument n’est plus un string, mais une liste :

['url1.html', 'url2.html']
<class 'list'>

Un nombre fixe de valeurs est appliqué avec l’option nargs=<int> :

import argparse

parser = argparse.ArgumentParser()

parser.add_argument('-a','--address', nargs=3, help='URLs to be indexed', required=True)
…

args = parser.parse_args()

print(args.address)
print(type(args.address))

Lorsque le nombre de valeurs reçu n’est pas celui attendu :

usage: googleindex.py [-h] -a ADDRESS [-j JFILE] [-y YEAR] [-v]
googleindex.py: error: argument -a/--address: expected 3 arguments

arguments Parent

Souvent des programmes partagent les mêmes arguments "parents" et ajoutent juste des arguments supplémentaires. On va éviter autant que possible la redondance de code. Utiliser l’argument parents lors de la création du parser dans le programme "enfant".

googleindex.py (script parent)
import argparse

def get_parser(h):
	parser = argparse.ArgumentParser(add_help=h)
	parser.add_argument("-a", "--address", nargs='*', help="URLs to be checked", required=True)
	parser.add_argument("-j", "--jsonauth",
						help="JSON Google Authentication file path, default $HOME/google-auth.json",
						default="/home/sqlpac/google-auth.json")
	
	return parser

if (__name__=="__main__"):
	p = get_parser(h=True)
	args = p.parse_args()
googleindexlang.py (script "enfant" ou child)
import googleindex
import argparse

def main(p):
	child_parser = argparse.ArgumentParser(parents=[p], add_help=True)
	child_parser.add_argument('-l','--lang', help='Language')
	
	args = child_parser.parse_args()


if (__name__=="__main__"):
	p = googleindex.get_parser(h=False)
	main(p)
python3 googleindexlang.py --help
  -h, --help            show this help message and exit
  -a [ADDRESS [ADDRESS ...]], --address [ADDRESS [ADDRESS ...]]
                        URLs to be checked
  -j JSONAUTH, --jsonauth JSONAUTH
                        JSON Google Authentication file path, default $HOME/google-auth.json
  -l LANG, --lang LANG  Language

L’option add_help est à False lorsqu’on récupère le parser parent dans le programme fils, sinon une erreur de conflit est levée :

argparse.ArgumentError: argument -h/--help: conflicting option strings: -h, --help

getopt

Le package getopt est moins puissant que le package argparse, il nécessite plus de code, code qui invoque le tableau système sys.argv.

  • Les arguments obligatoires et optionnels doivent être vérifiés manuellement
  • Les types de données doivent être également vérifiés manuellement

Mais il est bon de savoir comment lire/écrire et utiliser ce package.

import getopt, sys

def usage():
	print("Usage : %s --address <url> --jsonauth <json auth file path> --year <year selected> --version --help" % (sys.argv[0]))
	exit()

address = False
jsonauth = "/home/sqlpac/google-auth.json"
year = 2020
version = "1.0"

options, args = getopt.getopt(sys.argv[1:], 'a:j:y:h:v', ['address=',
														  'jsonauth=',
														  'year=',
														  'help',
														  'version'])
for opt, arg in options:
	if opt in ('-a', '--address'):
		address = arg
	elif opt in ('-j', '--jsonauth'):
		jsonauth = arg
	elif opt in ('-y', '--year'):
		year = arg
	elif opt in ('-v', '--version'):
		print(version)
		exit()
	elif opt in ('-h', '--help'):
		usage()

if not address:
	print('Address required')
	usage()

Ce morceau de code n’a pas besoin de commentaires, il est facile à lire même pour les débutants en sachant que la liste sys.argv[1:] stocke les arguments.

Quand on est habitués à la programmation shell ou C, on reconnaît les options courtes dans le second argument donné à la méthode getopt (a:j:y:h:v), les options longues sont appliquées dans le troisième argument (['address=','jsonauth=','help'…) avec l’opérateur = spécifié lorsqu’une valeur est attendue.

Conclusion

Sans aucun doute, le package argparse est plus puissant avec moins de code que le package getopt. Lequel ? Ça dépendra des préférences, de la complexité du projet…