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.

Contenu du fichier de validation

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.

Utilisation des objets de validation

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

Validation du travail de l'élève

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’un import *

  • utiliser le paramètre target_values à la place

  • supprimer la fonction avec un del pour ne pas qu’elle soit importée

  • utiliser 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])

Validation d'une fonction à deux paramètres

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)

Validation de la signature d'une fonction

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)

Validation de la signature d'une fonction sans les noms

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.