Langages de script - Python

Cours 5 - exceptions, logs, modules timeit et argparse

M2 Ingénierie Multilingue - INaLCO

Clément Plancq - clement.plancq@ens.fr

Exceptions

  • Les exceptions sont utilisées pour signaler que quelque chose d'anormal se produit, une erreur généralement

  • Les messages d'erreur générés par les exceptions donnent des détails précieux sur les erreurs (type, emplacement, ...)

  • Si elles sont levées par Python vous pouvez capturer les exceptions (try, except) et modifier le comportement de votre script en fonction

  • Vous pouvez également lever des exceptions avec raise

In [ ]:
chifoumi = ("pierre", "feuille", "ciseaux")
user_val = input("pierre, feuille, ciseaux ? ").lower()
if user_val not in chifoumi:
    raise ValueError("pierre, feuille ou ciseaux et puis c'est tout")

Capturer les exceptions

  • Trop facile
In [ ]:
val = [1, 2 ,3]
try:
  print(val[1984])
except IndexError:
  print("Erreur d'indice. Mais le roman est bien")
  • À condition de sélectionner la bonne classe d'exception
In [ ]:
dico = {'michel': 'H', 'michèle': 'F'}
try:
  print(dico[1984])
except IndexError:
  print("Erreur d'indice. Mais le roman est bien")
  • Les exceptions Python sont organisées en hiérarchie (voir ici), on finit par s'y retrouver

Capturer les exceptions

  • Utiliser la classe mère Exception réduit la précision de l'information sur l'erreur produite
In [ ]:
l = []
try:
    print(dico[l[5]])
except Exception:
  print("Soit la clé n'existe pas, soit l'indice n'existe pas")
  • Il est possible de capturer plusieurs types d'exceptions différentes
In [ ]:
l = [1,2,3,4,5]
try:
  print(dico[l[5]])
except IndexError:
  print("L'indice n'existe pas")
except KeyError:
  print("La clé n'existe pas")

Pour finir sur les exceptions

  • Le bloc else sera exécuté si aucune exception n'est levée

  • Le bloc finally sera exécuté dans tous les cas de figure

In [ ]:
l = [1,2,3,4,5,0]
try:
  print(dico[l[5]])
except IndexError:
  print("L'indice n'existe pas")
except KeyError:
  print("La clé n'existe pas")
else:
  print("OK tout roule")
finally:
  print("C'est fini, vous pouvez rentrer")

Logs

On peut toujours générer des logs manuellement à coups de if et de print mais ça devient vite fastidieux

Python embarque un module qui est fait pour ça : logging

In [ ]:
import logging

logging.basicConfig(filename='monscript.log',level=logging.INFO)
logging.debug('Celui-là ne sera pas dans les logs')
logging.info('Celui-là oui')

On peut formatter les log pour une sortie différente, souvent plus adaptée :

In [ ]:
import logging

logging.basicConfig(filename='monscript.log', level=logging.INFO, format="%(levelname)s\t%(asctime)s\t%(message)s")
logging.debug('Celui-là ne sera pas dans les logs')
logging.info('Celui-là oui')

Voir ici pour configurer logging.basicConfig et pour voir ce que l'on peut mettre pour l'argument format

Logs

Le module loguru est encore plus simple à utiliser.

Je vous encourage vivement à l'

Module timeit

  • timeit permet de mesurer le temps d'éxécution de portions de code ou de fonctions

  • La mesure du temps d'éxécution se fait en microseconde (usec), en milliseconde (msec) ou en secondes (sec)

  • Ce module s'utilise en ligne de commande, dans une console (i)python ou via des appels dans un script

  • Lors de l'appel en ligne de commande, le module détermine automatiquement le nombre de répétitions à réaliser

$ python3 -m timeit "[x**2 for x in range(100)]"
10000 loops, best of 3: 38.7 usec per loop

Module timeit

  • La fonction la plus souvent utilisée est timeit.timeit

  • Ce n'est pas la seule, voir la doc

In [ ]:
import timeit

def main():
    time_char_in_str = timeit.timeit('str="le chat"; char="a"; char in str', number=10000)
    print("Time char in str : {}".format(time_char_in_str))

    time_find = timeit.timeit("""\
str = "le chat"
char="a"
char.find(str)
    """, number=10000)
    print("Time find : {}".format(time_find))

main()

Module timeit

  • Vous pouvez donner accès aux fonctions individuellement via le paramètre 'setup'

  • Le plus pratique est d'utiliser le paramètre global=globals() pour lui passer l'espace de nom du script

In [ ]:
import timeit

def f(n):
    res = list()
    for x in range(n):
        res.append(x**2)
    return res

def g(n):
    return [x**2 for x in range(n)]

def main():
    try:
        print(timeit.timeit('f(10)', number=100000))
    except NameError:
        print("f n'est pas trouvée!") # on peut retirer le "try/catch" pour s'en convaincre
    print(timeit.timeit('f(10)', number=100000, setup="from __main__ import f"))
    print(timeit.timeit('g(10)', number=100000, globals=globals()))

main()
  • Dans une console ipython vous pouvez utiliser les mots clés magiques %timeit et %%timeit
In [1]:
def f(n):
    res = list()
    for x in range(n):
        res.append(x**2)
    return res
In [2]:
%timeit -n 100000 "f(10)"
11.1 ns ± 2.77 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [3]:
%%timeit
import random
l = [random.random() for xxx in range(100000)]
max(l)
8.58 ms ± 57.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [4]:
%%timeit import random; l = [random.random() for xxx in range(100000)]
max(l)
1.36 ms ± 115 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Module argparse

  • sys.argv permet de récupérer la liste des arguments passés à un script Python

  • Le module argparse est un parseur qui vous permettra de construire des interfaces en ligne de commande plus riches et plus simples à utiliser.
    Commencer avec le tutorial

In [ ]:
import argparse

parser = argparse.ArgumentParser(description="A useless script")
parser.add_argument("-v", "--verbose", help="verbose mode", action="store_true")
parser.add_argument("file", help="input file")
args = vars(parser.parse_args())

print(args['file'])
In [ ]:
bash
> python3 args.py
usage: args.py [-h] [-v] file
args.py: error: the following arguments are required: file

On peut également utiliser --help ou -h pour afficher l'aide

In [ ]:
bash
> python3 args.py -h
usage: args.py [-h] [-v] file

A useless script

positional arguments:
  file           input file

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

Pas de main en Python ?

Vous trouverez fréquemment le test suivant dans les scripts Python :

In [ ]:
if __name__ == '__main__':
    instruction1
    instruction2

ou

In [ ]:
def main():
    instruction

if __name__ == '__main__':
    main()

Cela évite que le code sous le test ne soit exécuté lors de l'import du script :
__name__ est une variable créée automatiquement qui vaut __main__ si le script a été appelé en ligne de commande, le nom du script s'il a été importé.

Accessoirement cela permet d'organiser son code et de le rendre plus lisible
Désormais je vous recommande vivement demande de l'inclure dans tous vos scripts

Exos

  1. Résoudre Scrabble