URL:     https://linuxfr.org/news/apprentissage-de-la-programmation-dans-les-lycees-snt-nsi-la-creation-d-exercices
Title:   Apprentissage de la programmation dans les lycées (SNT/NSI) — la création d’exercices
Authors: Claude SIMON
        tisaac, Ysabeau, Davy Defaud, Benoît Sibaud, ZeroHeure, ted et palm123
Date:    2019-11-09T15:28:34+01:00
License: CC by-sa
Tags:    nsi, snt, programmation, exercices, python, lycée et atlas_toolkit
Score:   4


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.

----

[Exercice de résolution d’une (in)équation du premier degré, avec une interface en mode texte](https://q37.info/s/bftf3kgb)
[Le même exercice, avec une interface graphique](https://q37.info/s/zkpdft9p)
[Le toolkit Atlas](https://atlastk.org)
[Son API](https://q37.info/s/gei0veus)
[De l’intérêt de moderniser les exercices de programmation](https://q37.info/s/knf9hdwp)
[Le dépôt GitHub de l’exercice](https://q37.info/s/t9dp9jcx)
[Le paquet « edutk » sur PyPi](https://q37.info/s/xhgwkn7v)
[La paquet du toolkit Atlas (« atlastk ») sur PyPi](https://q37.info/s/9srmskcm)
[Quelques autres exemples d’exercices basés sur cet outil](https://q37.info/s/tpkx4cfk)
[Une série d’exercices autour d’un jeu](https://q37.info/s/7sxtcv7g)

----

# 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 1^(er) 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.](https://q37.info/s/3brwhw9b.png)



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.](https://q37.info/s/qnw4cdht.png)



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](https://fr.wikipedia.org/wiki/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.](https://q37.info/s/tmzd3rzv.png)



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



## `workshop/Body.html`



```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`



```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`



Ce fichier contient les différents éléments nécessaires au fonctionnement de l’exercice et en constitue le cœur



```python
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.



```python
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.



```python
_.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 à :



```python
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*.



```python
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.



```python
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).



```python
def _acConnect(c, dom):
 dom.setLayout("", _.read(os.path.join("workshop", "Body.html"), c.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.



```python
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.



```python
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écedemment. 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`



```python
import workshop.core as _
```















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



```python
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`.



```python
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.



```python
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`



```python
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.



```python
from workshop.fr import *
```















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



```python
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.



```python
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.



---



*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.*