bas de page
 

Apprentissage de la programmation
dans les lycées (SNT/NSI)
la création d'exercices

Claude Simon
 

   Depuis cette rentrée 2019, les élèves en classe de seconde ont un cours obligatoire intitulé Sciences numériques et technologie (SNT), alors que les élèves en classe de première, puis lors de leur passage en classe de terminale, peuvent opter pour un enseignement intitulé Numérique et sciences informatiques (NSI).

   Dans le cadre de ces cours, les élèves auront naturellement des exercices à faire qui consisteront, entre autres, à écrire des programmes en Python (version 3 ou supérieure), le langage retenu par l'Éducation nationale. Néanmoins, notamment à cause de leur interface, ces programmes renvoient une image désuète de l'informatique, en décalage avec celle véhiculée par les smartphones, très populaires auprès des jeunes.

   Cette dépêche présente un outil dédié à la création d'exercices modernes de programmation, c'est-à-dire des exercices reflétant les nouveaux usages de l'informatique, apparus avec la popularisation des smartphones. Cet outil confère à ces exercices une forme nouvelle propre à stimuler l'intérêt des élèves pour ces cours de SNT / NSI, en faisant le lien entre l'informatique telle qu'abordée dans ces cours, et celle à laquelle ils sont habitués de par l'utilisation de leur smartphone.

Avant propos

   Malgré l'émergence de certains langages, les exercices de programmation n'ont guère évolué ces dernières décennies. Par exemple, un exercice de résolution d'(in)équation du 1er degré – un classique, les cours de programmation étant généralement assurés par des professeurs de mathématiques – ressemble à ça :

   Image montrant le résultat de l'exécution d'un programme de résolution d'une inéquation du premier degré dotée d'une interface texte, avec laquelle l'utilisateur saisit les différents paramètres de l'inéquation l'un après l'autre, le forçant à en ressaisir la totalité même s'il ne veut en changer qu'un seul.

   C'est assez austère, et l'ergonomie laisse à désirer, car il faut ressaisir systématiquement l'ensemble des paramètres, même si l'on ne veut en changer qu'un seul. Il est certes possible d'améliorer l'interface ou de gérer les paramètres en tant qu'arguments passés au programme lors de son invocation. Toutefois, cela devient rapidement trop compliqué à programmer par rapport à la finalité de l'exercice, et l'on ne pourra jamais obtenir le confort d'utilisation que confère une interface graphique.

   Les interfaces graphiques sont justement le type d'interface auquel les jeunes sont habitués. Ce sont elles que l'on retrouve dans la majorité, voire la totalité des applications qu'ils utilisent sur leur smartphone. Ils n'ont probablement jamais eu affaire à une interface textuelle telle que celle mise en œuvre dans l'exemple ci-dessus.

   Supposons que l'on modifie l'exercice ci-dessus pour lui donner la forme suivante :

   Image montrant le résultat de l'exécution d'un programme de résolution d'une inéquation du premier degré avec une interface graphique, qui affiche un formulaire par lequel l'utilisateur peut modifier un des paramètres de l'inéquation sans avoir à ressaisir les autres.

   Sous cette forme, l'exercice est bien plus attrayant et d'une bien meilleure ergonomie que sous la précédente forme.

   Cette dépêche ne porte pas sur la pertinence de cette approche, dont vous trouverez un lien vers une description plus détaillée en fin de dépêche, mais sur la solution technique qui permet de la mettre en œuvre.

   Cette solution s'appuie sur le toolkit Atlas, qui a l'avantage d'être simple à mettre en œuvre, facile à utiliser et léger (un lien vers le site dédié à ce toolkit, ainsi qu'un autre détaillant son API, sont disponibles en fin de dépêche). Cependant, bien que ce toolkit simplifie considérablement la manipulation de l'interface, cela reste trop compliqué pour des débutants en programmation.

   C'est sur la couche logicielle qui permet l'écriture d'exercices adaptés au niveau de l'élève, notamment concernant la manipulation de l'interface, que cette dépêche va s'attarder. Pour cela, une série de fichiers, tirés d'un exercice mettant en œuvre cette couche logicielle, vont être passés en revue l'un après l'autre. L'adresse du dépôt GitHub qui contient la totalité des fichiers constituant cet exercice est donnée en fin de dépêche.

   Cette couche logicielle est disponible sous forme d'un paquet PyPi nommé edutk (voir en fin de dépêche). Néanmoins, il sera embarqué directement dans l'archive contenant l'exercice, et non pas installé via un pip install ..., ceci dans le but de simplifier l'installation de l'exercice. Cela vaut également pour le paquet relatif au toolkit Atlas (atlastk).

Principales caractéristiques

   Par rapport à sa forme classique, un exercice mettant en œuvre cette solution présente les particularités suivantes, dont les trois premières, gérées par le toolkit Atlas, ne seront pas détaillées dans cette dépêche, contrairement aux trois dernières.

Utilisation d'une interface graphique

   Dans la plupart des cas, un exercice consistera en l'écriture d'un programme avec lequel l'utilisateur peut interagir. Ces interactions se feront alors via une interface graphique, plus précisément une interface web. Lorsque l'exercice est lancé, un navigateur web s'ouvrira automatiquement pour afficher l'interface de l'exercice.

Accès facile depuis Internet

   Dès son lancement, l'exercice est automatiquement et instantanément accessible de n'importe quel dispositif (smartphone, tablette...) connecté à Internet et équipé d'un navigateur web moderne. Cet accès est facilité par le fait de ne pas avoir :

  • à déployer le code de l'exercice sur un serveur distant ;
  • ni à configurer un routeur pour ouvrir et rediriger un port ;
  • ni à configurer le dispositif pour le connecter au réseau local.

   Il suffit, en effet, que l'ordinateur hébergeant l'exercice ainsi que le dispositif avec lequel on veut y accéder soient tous les deux connectés à Internet.

Multi-utilisateur

   Comme indiqué, l'exercice est une application web, qui plus est facilement accessible de n'importe où sur Internet. De ce fait, il est possible que plusieurs personnes accèdent, simultanément, au même exercice. Cette situation ne pose pas de problèmes, les mécanismes nécessaires à sa gestion étant mis en œuvre de manière automatique et totalement transparente.

Gestion simplifiée de l'interface

   L'exercice pourra être élaboré de manière à ce que les instructions nécessaires à la manipulation de l'interface soient adaptées au niveau de difficulté de l'exercice. Au besoin, ces instructions pourront être aussi simples à utiliser que les classiques input(...) et print(...).

Internationalisation simplifiée

   Bien que les identifiants des différents objets, classes, fonctions... que l'élève doit implémenter peuvent être adaptés à la langue de l'élève, l'accès à chacun de ces éléments, pour le créateur de l'exercice, se fera via un identifiant unique. Ainsi, le code constituant le cœur de l'exercice n'aura pas à être adapté à chacune des langues pour lequel l'exercice sera disponible.

Redirection des messages d'erreur

   Comme l'application s'exécute dans un navigateur web, l'élève n'aura pas forcément le réflexe de regarder la console à partir de laquelle l'application est lancée lorsque son code lève une exception. Il ne comprendra donc pas pourquoi son application ne fonctionne pas. C'est pourquoi, par défaut, les exceptions s'affichent dans une boîte de dialogue. Cependant, parce que cela est plus pratique durant l'élaboration de l'exercice, il sera possible de rétablir l'affichage des erreurs en console.

L'exercice côté enseignant

   L'exercice proposé à titre d'exemple est une version un peu plus évoluée de l'habituelle Hello World (probablement pas le premier exercice que l'on va soumettre à un débutant), qui ressemble à ceci :

   Image montrant le résultat de l'exécution du fameux « Hello World » avec une interface graphique, affichant un message de salutation reprenant le nom saisi dans un champ texte prévu à cet effet.

   Comme annoncé, chacun des fichiers constituant l'exercice va maintenant être passé en revue.

workshop/Body.html

<div style="display: table; margin: 50px auto auto auto;">
  <fieldset>

    <div style="display: table; margin: auto auto 10px auto;">
      <input id="input"
        placeholder="$NameToDisplay$"
        data-xdh-onevent="Submit"
        maxlength="15"/>
    </div>

    <fieldset class="hidden"
        style="border: 1px solid; padding: 10px;box-shadow : 5px 5px;"
        id="output"/>
    </fieldset>

</div>

   Ce fichier contient l'interface HTML de l'exercice. Il n'y a rien de particulier, mis à part l'attribut data-xdh-onevent, qui sera abordé plus tard, ainsi que le contenu de l'attribut placeholder ($NameToDisplay$), avec la présence du symbole $ qui sera expliquée par la suite. On retrouvera la valeur des différents attributs id dans d'autres fichiers.

   On peut remarquer que l'on a à disposition toute la puissance du CSS. On peut ainsi facilement modifier l'apparence de l'interface en rajoutant des couleurs, des formes, des animations..., bref, tout ce qui est susceptible de renforcer l'aspect ludique de l'exercice.

workshop/Head.html

<style>
  .hidden {
   display: none;
  }
</style>

   Le contenu de ce fichier est placé dans la section head de l'interface HTML. Il contient généralement la définition des règles CSS utilisées dans le fichier workshop/Body.html ou, plus généralement, utilisées par l'application.

workshop/core.py

<div style="display: table; margin: 50px auto auto auto;">
  <fieldset>

    <div style="display: table; margin: auto auto 10px auto;">
    <input id="input"
      placeholder="$NameToDisplay$"
      data-xdh-onevent="Submit"
      maxlength="15"/>
    </div>

    <fieldset class="hidden"
      style="border: 1px solid; padding: 10px;box-shadow: 5px 5px;"
      id="output"/>

  </fieldset>
</div>

   Ce fichier contient les différents éléments nécessaires au fonctionnement de l'exercice et en constitue le coeur

import edutk as _
from edutk import Core

   On importe le paquet destiné à faciliter l'écriture de l'exercice, paquet à l'API duquel on accédera via l'identifiant _. L'objet Core, lui, est rendu directement accessible.

F_HELLO = "Hello"

_.defineUserItem(globals(), "uf", F_HELLO)

   On déclare là une fonction que l'élève devra définir et par laquelle le concepteur de l'exercice accédera via la fonction d'identifiant ufHello. Celle-ci n'appelle pas directement la fonction définie par l'élève, mais en retourne une référence. Grâce à cela, l'identifiant utilisé pour cette fonction dans le prototype exposé à l'élève sera différent de celui donné ici (ufHello). On verra plus tard comment cela est mis en œuvre.

_.setEnums(globals(),"label",("MissingName", "NameToDisplay"))

   Cela définit une classe label avec deux membres MissingName et NameToDisplay avec, comme contenu, la chaîne de caractère contenant leur nom. Concrètement, la fonction ci-dessus est équivalente à :

class label:
  MissingName = "MissingName"
  NameToDisplay = "NameToDisplay"

   Cela sert à définir les labels qui devront être traduits.

   Notez le membre NameToDisplay qui correspond à la valeur de l'attribut placeholder, les $ en moins, présent dans le fichier workshop/Body.html.

   Les différentes chaînes de caractères définies ici vont, comme on le verra plus bas, être utilisées comme clef d'un dictionnaire, et doivent donc être uniques. Le fait que ces chaînes de caractères reprennent le libellé du membre auquel elles sont affectées n'est qu'un moyen de garantir cette unicité, vu que chaque membre au sein d'une classe doit porter un identifiant unique. Le but est en fait d'obtenir l'équivalent d'un enum.

def clear():
  _.dom().setLayout("output", "<span/>")

   Cela définit une première fonction de l'API à utiliser par l'élève dans la réalisation de l'exercice. Cette fonction procède à l'effacement de la zone d'affichage, output étant la valeur de l'attribut id associé à cette zone dans le fichier workshop/Body.html, en écrasant son contenu par la chaîne de caractères <span/>. L'objet retourné par la fonction dom(), ainsi que la méthode associée setLayout(...), sont fournies par l'API du toolkit Atlas, dont vous trouverez une description détaillée dans la documentation.

   Notez que, comme vous le verrez par la suite, l'identifiant donné à la fonction (clear) n'est pas l'identifiant par lequel l'élève accédera à cette fonction. Ce sera également le cas pour toutes les autres fonctions de l'API destinée à l'élève.

def display(text):
  output = _.Atlas.createHTML()
  output.putTagAndValue("h1", text)
  _.dom().appendLayout("output", output)

   Seconde fonction de l'API destinée à être utilisée dans l'exercice. En utilisant le toolkit Atlas, elle crée un arbre HTML constitué d'une balise h1 contenant le texte à afficher. Cet arbre est ensuite ajouté à la zone d'affichage (on retrouve le même identifiant output que précédemment).

def _acConnect(c, dom):
  dom.setLayout("", _.read(os.path.join("workshop", "Body.html"),
  .body_i18n))
  dom.focus("input")

   Cette fonction sera appelée en interne à chaque lancement d'une nouvelle session utilisateur (on la retrouvera lus bas). Elle affiche le contenu du fichier workshop/Body.html en remplaçant, grâce à la méthode .read(...), le label $NameToDisplay$ (ainsi que toute chaîne de caractères délimitée par $) par sa traduction contenue dans c.body_i18n, que l'on verra par la suite. Les autre fonctions appartiennent à l'API du toolkit Atlas, dont la documentation vous fournira les détails.

   Le paramètre c est abordé plus loin, et le paramètre dom est fourni par le toolkit Atlas. Ce sont ces deux mêmes paramètres qui sont passés à la fonction ci-dessous.

def _acSubmit(c, dom) :
  input=dom.getContent("input").strip()

  if (len(input))!= 0:
    ufHello()(dom.getContent("input"))
    dom.setContent( "input", "")
    dom.removeClass("output", "hidden")
  else :
    dom.alert(c.i18n[label.MissingName])
    dom.setContent("input", "")
    dom.focus("input")

   Cette fonction interne, que l'on retrouvera plus bas, sera appelée lorsque l'utilisateur valide sa saisie. C'est du Python pur jus accompagné d'appels à des méthodes du toolkit Atlas. On retrouve ici la fonction ufHello définie plus haut. Elle permet d'appeler la fonction que l'élève doit définir dans le cadre de l'exercice. input est la valeur de l'attribut id du champ texte défini dans le fichier workshop/Body.html.

   Notez les deux paires de parenthèses utilisées lors de l'appel à ufHello. La première sert à retourner une référence à la fonction définie par l'élève, la seconde à appeler cette fonction avec les paramètres adéquats.

   L'objet label est celui défini plus haut. Quant à c.i18n, on le retrouvera plus bas.

def main(callback, globals, userFunctionLabels, title):
  # Uncomment to display exceptions in terminal,
  # instead of being displayed in an alert box.
  #_.useRegularExceptions()
  _.assignUserItems((F_HELLO,), globals, userFunctionLabels)
  _.main( os.path.join("workshop", "Head.html"), callback, {
    "" : _acConnect,
    "Submit": _acSubmit,
  }, title
)

   Cette fonction sert à lancer l'application qu'il y a derrière l'exercice.

   callback est la fonction à appeler pour créer un objet utilisateur. Cette fonction est appelée à chaque nouvelle session et c'est la valeur retournée qui est passée en tant que paramètre c aux fonctions _acConnect et _acSubmit ci-dessus.

   Le paramètre globals contient l'objet retourné par la fonction globals() lorsqu'elle est lancée par l'appelant.

   userFunctionLabels contient les liens entre les identifiants de fonctions tels que définis par le concepteur de l'exercice, et ceux, internationalisés, utilisés dans les prototypes exposés à l'élève. C'est grâce à cet objet que l'appel à la fonction ufHello() va retourner la fonction définie par l'élève.

   title est la chaîne de caractère qui sera le titre de l'application ; son contenu est placé dans la balise title de la section head de la page HTML de l'application.

   Durant l'élaboration de l'exercice, pour que les exceptions s'affichent dans la console, et non pas dans une boîte de dialogue, il suffira de décommenter l'appel à _.useRegularExceptions().

   L'appel à _.assignUserItems(...) permet d'indiquer les identifiants des fonctions, ou d'autres items (classes, variables...), que l'élève doit définir (ici, il n'y a que F_HELLO). Ce seront ces identifiants qui seront utilisés dans les prototypes qui lui seront fournis.

   _.main(...) lance l'application. Le premier paramètre est le nom du fichier contenant les éléments à placer dans la section head de la page HTML de l'exercice, en l'occurrence le fichier workshop/Head.html, dont le contenu a été détaillé précédemment. Le troisième paramètre permet d'associer une action à une fonction. La chaîne vide correspond à l'action qui est appelée lors du lancement d'une nouvelle session et qui est ici associée à la fonction    _acConnect(...) précédemment définie. La chaîne Submit correspond à l'action telle que définie par la valeur de l'attribut data-xdh-onevent du fichier workshop/Body.html et est ici associée à la fonction _acSubmit(...) précédemment définie.

workshop/fr.py
import workshop.core as _

   Importe le fichier workshop/core.py détaillé ci-dessus, dont l'API est rendu accessible via l'identifiant _.

class _Core(_._.Core):
  i18n = {
    _.label.MissingName: "Veuillez saisir un prénom !"
  }
  body_i18n = {
    _.label.NameToDisplay: "Prénom"
  }

  def __init__(self, dom):
    _.Core.__init__(self, dom)

   Une instance de l'objet _Core va être créée lors de chaque nouvelle session. Le concepteur de l'exercice peut y placer toutes les données nécessaires au fonctionnement de cet exercice. Le membre i18n contient les traductions des messages affichées par l'application et le membre body_i18n, les traductions des chaînes de caractères délimitées par un $ présentes dans le fichier workshop/Body.html.

efface = _.clear
affiche = _.display

   Ces deux lignes créent des alias entre les identifiants des fonctions de l'API destinée à être utilisée par l'élève (définies dans workshop/core.py) et les identifiants par lesquels l'élève appellera ces fonctions.

def go(globals):
  _.main(lambda dom: _Core(dom), globals, {_.F_HELLO:
  _ "afficheSalutations"}, "Atelier de programmation")

   Fonction appelée à la fin du code écrit par l'élève.

   Le premier paramètre de l'appel à _.main(...) est un callback qui sera appelé à chaque nouvelle session et qui devra fournir une instance d'un objet contenant les éléments propres à cette session. Ici, ce callback retourne une instance de la classe _Core précédemment définie, dom étant un objet crée et utilisé par le toolkit Atlas. globals doit contenir le résultat de l'appel à globals() à partir du code de l'élève. Le troisième paramètre est le titre de l'exercice.

workshop/en.py

import workshop.core as _

class _Core(_.Core):
    i18n = {
        _.label.MissingName: "Please enter a first name!"
    }
    body_i18n = {
        _.label.NameToDisplay: "First name"
    }

    def __init__(self, dom):
      _.Core.__init__(self, dom)

erase = _.clear
display = _.display

def go(globals):
    _.main(lambda dom: _Core(dom), globals, {_.F_HELLO:
  _ "displayGreetings"}, "Programming workshop")

   Il s'agit simplement de la version anglaise du fichier précédemment décrit.

L'exercice vu de l'élève

   L'objet de tous les fichiers de la précédente section est de ne donner à manipuler par l'élève qu'un seul fichier, le plus concis possible. C'est pour cela que cette section contraste par sa brièveté avec la précédente.

Version française

   Contenu du fichier fr.py situé à la racine du dépôt.

from workshop.fr import *

   Importation du fichier workshop/fr.py (voir ci-dessus) avec accès direct à son API.

def afficheSalutations(nom):
# début du code à saisir par l'élève
  efface()
  affiche("Bonjour, " + nom + " !")
  affiche("Bonne journée, " + nom + " !")
# fin du code à saisir par l'élève

go(globals()

   On voit que l'identifiant de la fonction à définir par l'élève est afficheSalutations comme déclaré dans le ficher workshop/fr.py et non pas ufHello qui est déclaré dans le fichier workshop/core.py. De la même manière, c'est efface() et affiche(...) qui sont utilisés et non pas clear() et erase(...).

Version anglaise

   Contenu du fichier en.py situé à la racine du dépôt.

from workshop.en import *

def displayGreetings(name):
  erase()
  display("Hello, " + name + "!")
  display("Have a good day, " + name + "!")

go(globals())

   Dans cette version anglaise, dans laquelle on importe workshop.en à la place de workshop.fr, c'est displayGreetings(...), erase() et display(...) qui sont respectivement utilisés à la place de afficheSalutations(...), efface() et affiche(...).

Conclusion

   edutk est considéré comme étant en version bêta de par son API. Cette API est, en effet, amenée à évoluer dans le but d'aboutir à un outil qui puisse être utilisé par les futurs titulaires du CAPES d'informatique, qui devrait voir le jour en 2020, pour créer leurs propres exercices. On peut aussi envisager de faire évoluer cet outil de manière à ce qu'il puisse être utilisé par les élèves des classes de terminale ayant choisi l'option NSI pour créer, à titre d'exercice, des exercices pour leurs petits camarades des classes de première et seconde.

   En complément du contenu de cette dépêche, ceux qui désirent approfondir leur compréhension du fonctionnement et de la mise en œuvre de cet outil trouveront, en fin de dépêche, deux liens pointant sur des exemples d'exercices plus sophistiqués.

   Toutes les personnes qui désirent utiliser cet outil, notamment celles qui officient actuellement dans les cours de SNT / NSI, sont, naturellement, les bienvenues. Conscient que beaucoup d'entre elles n'ont été que peu ou pas formées dans le domaine de la programmation, je me ferais un plaisir de les assister autant qu'il m'est possible dans leur utilisation de cet outil.

Claude Simon

   Merci à tous celles et ceux qui, notamment en apportant des corrections ou en suggérant des modifications, ont contribué à l'amélioration du contenu de cette dépêche.

Paru sur Linuxer.odg 23 novembre 2019.
https://linuxfr.org/news/apprentissage-de-la-programmation-dans-les-lycees-snt-nsi-la-creation-d-exercices

Cet article est sous licence Creative Commons (selon la juridiction française = Paternité - Pas de Modification). http://creativecommons.org/licenses/by-nd/2.0/fr/

Aller plus loin

Exercice de résolution d'une (in)équation du premier degré, avec une interface en mode texte (12 clics)
https://linuxfr.org/redirect/105216

Le même exercice, avec une interface graphique (29 clics)
https://linuxfr.org/redirect/105217

Le toolkit Atlas (17 clics)
https://linuxfr.org/redirect/105218

Son API (5 clics)
https://linuxfr.org/redirect/105219

De l'intérêt de moderniser les exercices de programmation (20 clics)
https://linuxfr.org/redirect/105220

Le dépôt GitHub de l'exercice (6 clics)
https://linuxfr.org/redirect/105221

Le paquet « edutk » sur PyPi (10 clics)
https://linuxfr.org/redirect/105258

La paquet du toolkit Atlas (« atlastk ») sur PyPi (6 clics)
https://linuxfr.org/redirect/105259

Quelques autres exemples d'exercices basés sur cet outil (6 clics)
https://linuxfr.org/redirect/105278

Une série d'exercices autour d'un jeu (5 clics)
https://linuxfr.org/redirect/105279

haut de page
Association EPI
Décembre 2019

Accueil Informatique et TIC Articles