Auto-évaluation#
Dans une activité Python avec Capytale, il est possible d’apporter un retour aux élèves
sur leur travail. Nous mettons à disposition plusieurs outils disponibles dans
le module capytale.autoeval comme :
Note
Le mécanisme d’auto-évaluation est très utilisé dans le cadre d’un notebook séquencé.
Principe général de l’auto-évaluation#
Dans un premier temps, l’enseignant commence par écrire un fichier Python
contenant le code permettant de valider le travail de l’élève.
Ceci revient à créer des objets utilisant les classes du module capytale.autoeval,
comme ci-dessous par exemple.

Tip
Il est judicieux que le nom du fichier Python commence par un _,
comme par exemple _validation.py de manière à masquer son contenu
aux élèves.
Ensuite, l’enseignant attache ce fichier à son activité de manière à rendre ce module Python importable puis il place l’instruction d’import en début d’activité :
from _validation import *
Enfin, il place dans son activité les appels aux objets de validation aux endroits appropriés.

Ainsi, lorsque l’élève travaillera sur l’activité, il aura des retours riches sur son travail.

Tip
Dans le cas d’un notebook, lors de l’écriture du code de validation par l’enseignant, il peut être avantageux d’écrire ce code directement dans le notebook (dans la première cellule par exemple) afin de tester facilement son activité au fur et à mesure.
Ce n’est qu’une fois que l’on a terminé, que l’on peut placer ce code dans un fichier
nommé _validation.py par exemple et l’importer.
Valider l’existence et la valeur de variables#
Pour valider la définition de variables, on utilise un objet de la
ValidateVariables. On le construit avec
un dictionnaire dont les clefs sont les noms des variables et dont
les valeurs sont celles attendus.
from capytale.autoeval import ValidateVariables
test_variables = ValidateVariables({"pi": 3.14, "chaine": "abracadabra", "premiers": [2, 3, 5, 7]})
L’utilisateur recevra un message indiquant soit que la variable n’est pas définie, soit qu’elle n’a pas la bonne valeur, soit que tout est conforme.
Valider l’implémentation d’une fonction#
Pour valider une fonction sur des valeurs, on utilise les classes
ValidateFunction
ou
ValidateFunctionPretty
qui en plus affiche les résultats des tests dans un joli tableau.
Il est nécessaire de fournir une liste de valeurs sur lesquelles valider la fonction ainsi que, la liste des retours correspondants ou une fonction de correction.
from capytale.autoeval import ValidateFunction
test_carre = ValidateFunction("carre", test_values=[-2, -1, 0, 1, 2], target_values=[4, 1, 0, 1, 4])
test_valeur_absolue = ValidateFunction("valeur_absolue", test_values=[-2, -1.5, 0, 1.1, 2], valid_function=abs)
L’utilisateur recevra un message indiquant soit que la fonction n’est pas définie, soit qu’un test n’est pas passé, soit que tout est conforme.
Danger
Attention lors de l’utilisation du paramètre valid_function. Si vous donnez comme
nom à votre fonction le même nom que celui que vous attendez de l’élève,
l’instruction from _validation import * va aussi importer votre fonction qui n’aura
donc même pas a être implémentée par l’élève pour passer les tests…
Il y a plusieurs moyens d’éviter ceci :
ne pas nommer la fonction de validation comme celle attendue
faire débuter le nom de sa fonction par un
_pour empêcher son import lors d’unimport *utiliser le paramètre
target_valuesà la placesupprimer la fonction avec un
delpour ne pas qu’elle soit importéeutiliser le mécanisme
__all__des modules de Python pour exporter seulement le nécessaire lors d’un import*.
Valider des fonctions à plusieurs paramètres#
Il est tout à fait possible d’utiliser les classes
ValidateFunction et
ValidateFunctionPretty
avec des fonctions à plusieurs paramètres. Pour cela, il faut penser à passer
les valeurs de test sous forme de tuples.
Par exemple, si l’on demande à l’élève d’implémenter une fonction xor (ou exclusif),
on pourra la valider comme ceci :
from capytale.autoeval import ValidateFunctionPretty
# peut aussi être généré avec itertools.product([True, False], repeat=2)
values = [(True, True), (True, False), (False, True), (False, False)]
test_xor = ValidateFunctionPretty("xor", values, target_values=[x != y for x, y in values])

Valider la signature d’une fonction#
Par défaut, la signature (le nombre de paramètres, leur nom, leur type et le type de retour)
des fonctions n’est pas testé. Cependant, les classes ValidateFunction
et ValidateFunctionPretty mettent à disposition un mécanisme permettant
d’accomplir ceci.
Le paramètre utile du constructeur est
check_signature. C’est un booléen
qui va indiquer de comparer la signature de la fonction de l’élève à celle de la
fonction passée grâce au paramètre valid_function.
from capytale.autoeval import ValidateFunction
def _abs(x: float) -> float:
return x if x > 0 else -x
test_valeur_absolue = ValidateFunction(
"valeur_absolue", [x / 10 for x in range(-20, 21)],
valid_function=_abs, check_signature=True)

Note
Si l’on préfère ne pas utiliser le paramètre valid_function,
il est possible de passer directement la signature à tester au
paramètre check_signature.
from capytale.autoeval import ValidateFunction
import inspect
def _correction(x: float) -> float:
pass
valeurs = [x / 10 for x in range(-20, 21)]
test_valeur_absolue = ValidateFunction(
"valeur_absolue", valeurs, target_values=[abs(x) for x in valeurs],
check_signature=inspect.signature(_correction))
Tip
Si l’on ne souhaite pas tenir compte du nom des paramètres, on peut utiliser
ignore_names_in_signature=True dans le constructeur.
from capytale.autoeval import ValidateFunction
def _abs(x: float) -> float:
return x if x > 0 else -x
test_valeur_absolue = ValidateFunction(
"valeur_absolue", [x / 10 for x in range(-20, 21)],
valid_function=_abs, check_signature=True,
ignore_names_in_signature=True)

Mécanismes d’auto-évalutation avancés#
Validation forte et validation faible#
En cours de rédaction.
Définir sa propre classe de validation#
La validation est un processus potentiellement très générique. Ainsi,
l’enseignant peut tout à fait créer ses propres mécanismes de validation.
Pour cela, il est nécessaire de définir une classe en utilisant le
décorateur @validationclass
qui s’occupe de faire fonctionner la machinerie de validation.
Note
Pour ceux qui ne connaissent pas les décorateurs, il suffit de placer
l’instruction @validationclass juste au dessus de la définition de la
classe.
Il faut ensuite définir la méthode __call__ pour indiquer comment
s’effectue la validation. La valeur de retour est importante : il
faut penser à retourner True en cas de succès et False en cas
d’échec. Le décorateur @validationclass
s’occupe automatiquement d’avancer dans un
notebook séquencé en cas de succès.
Il s’occupe aussi de compter les nombres de tentatives
de validation.
Par exemple, si l’enseignant souhaite vérifier que l’élève a bien
importé le module math, il peut s’y prendre comme ceci
from capytale.autoeval import validationclass
import math as _math
import __main__
import sys
@validationclass
class ValidateMathModule:
def __call__(self):
if hasattr(__main__, "math") and __main__.math == _math:
print("👏 Bravo, tu as bien importé le module math !")
return True
else:
print("Le module math n'est pas importé, attention à ne pas faire import * !",
file=sys.stderr)
return False
test_math = ValidateMathModule()
Note
Dans le cas particulier de l’exemple ci-dessus, on aurait en fait pu utiliser la classe
ValidateVariables.
from capytale.autoeval import ValidateVariables
import math as _math
test_math = ValidateVariables({"math": _math})
mais il aurait fallu travailler un peu pour customiser les messages.
Customiser les messages#
En cours de rédaction
Tenir compte du nombre de tentatives#
Les classes de validation implémentent un mécanisme de comptage du nombre d’essais. Il est ainsi facile de changer le comportement de la validation en fonction du nombre d’essais.
Par exemple, pour donner un indice à l’élève après 5 tentatives, on peut s’y prendre ainsi
from capytale.autoeval import ValidateVariables, validationclass
@validationclass
class ValidateHarshad(ValidateVariables):
def __init__(self):
super().__init__({"nombres_harshad": [1, 2, 3, 4, 5, 6, 7, 8, 9, 12]})
def __call__(self):
success = super().__call__()
if not success and self.trial_count() >= 5:
print("Je te donne un indice : un entier à un chiffre est toujours un nombre harshad.")
return success
dix_premiers_harshad = ValidateHarshad()
Réunir plusieurs validations en une seule#
En cours de rédaction.
Pour réunir plusieurs validation en une seule, on utilise la classe
ValidateAll.