Lexique — Première & Terminale
Interactions web et programmation
Interactions entre l'utilisateur et une page Web
Événements et gestionnaires d'événements
Une page Web est interactive : elle réagit aux actions de l'utilisateur grâce à des composants graphiques (liens, boutons, champs de texte, cases à cocher, listes déroulantes...) et à un langage exécuté dans le navigateur, JavaScript.
Un événement est une action détectée par le navigateur : un clic de souris, un passage du curseur sur un élément, une saisie au clavier, la soumission d'un formulaire, etc. Un gestionnaire d'événement (event handler) est le code exécuté en réaction à un événement précis, associé à un élément de la page.
<button onclick="maFonction()">Cliquer ici</button>function maFonction() {
alert("Le bouton a ete clique !");
}À noter. Le code du gestionnaire n'est exécuté qu'au moment où l'événement se produit (ici, le clic), et non au chargement de la page : c'est toute la différence entre une instruction écrite directement dans un fichier JavaScript (exécutée dès le chargement) et une instruction placée dans une fonction associée à un événement.
Exemple. Modifier la couleur d'un texte au clic sur un bouton, grâce à document.querySelector, qui retrouve un élément de la page à partir d'un sélecteur :
<p id="monPara">Cliquez sur un bouton</p>
<button onclick="document.querySelector('#monPara').style.color='red'">Rouge</button>Ici, l'événement onclick déclenche une instruction JavaScript qui va chercher l'élément d'identifiant monPara et modifie la propriété CSS color de son style.
Le modèle client-serveur
Sur le Web, deux machines communiquent selon des rôles différents : le client (le navigateur de l'utilisateur) demande des ressources, et le serveur les fournit. Visiter une page déclenche un échange client-serveur : le client demande d'abord le fichier HTML, puis les ressources qu'il référence (images, CSS, JavaScript).
Il est essentiel de distinguer ce qui s'exécute côté client de ce qui s'exécute côté serveur, et dans quel ordre :
- le client envoie une requête au serveur ;
- le serveur exécute, si besoin, un programme qui génère la réponse (page HTML) : c'est l'exécution côté serveur ;
- le serveur renvoie sa réponse ;
- le navigateur affiche la réponse puis exécute le JavaScript qu'elle contient : c'est l'exécution côté client.
Le JavaScript reçu par le navigateur s'exécute donc toujours après le serveur, et uniquement côté client.
Requêtes et réponses HTTP
HTTP (HyperText Transfer Protocol) est l'ensemble des règles qui régissent l'échange entre client et serveur : le client envoie une requête, le serveur renvoie une réponse.
Une requête contient notamment une méthode (l'action demandée) et l'URL de la ressource visée. Les deux méthodes les plus courantes :
| Méthode | Rôle |
|---|---|
GET | demander une ressource, sans effet sur le serveur ; les paramètres apparaissent dans l'URL |
POST | soumettre des données au serveur pour traitement ; les paramètres sont dans le corps de la requête, non visibles dans l'URL |
Une réponse contient notamment un code de statut (200 : ressource trouvée, 404 : ressource introuvable) et un corps (par exemple le code HTML de la page).
Le protocole HTTP est sans état : le serveur ne se souvient pas d'une requête à l'autre. Un cookie est une petite donnée que le serveur demande au navigateur de mémoriser, et que celui-ci retransmet automatiquement à chaque nouvelle requête vers ce serveur, ce qui permet par exemple de garder un utilisateur connecté. Le HTTPS est la version chiffrée de HTTP : les données échangées deviennent illisibles pour un tiers qui intercepterait la communication.
Le formulaire d'une page Web
Un formulaire HTML permet à l'utilisateur de saisir des données et de les transmettre au serveur. Il est délimité par la balise <form>, avec deux attributs essentiels : action (l'URL de destination) et method (la méthode HTTP employée, get ou post).
<form action="traitement.php" method="post">
<label>Nom</label> : <input type="text" name="nom" />
<input type="submit" value="Envoyer" />
</form>Exemple. Avec la méthode GET, les données saisies apparaissent directement dans l'URL, sous la forme ?nom=Martin&ville=Lyon : elles sont visibles et modifiables dans la barre d'adresse, ce qui convient pour une recherche mais pas pour une donnée sensible comme un mot de passe. Avec la méthode POST, les données sont transmises dans le corps de la requête, non visibles dans l'URL — mais attention, cela ne les chiffre pas : sans HTTPS, elles restent lisibles par quiconque intercepte la requête.
Programmer : modularité, paradigmes et mise au point
Modularité : modules, bibliothèques et API
La modularité consiste à découper un programme en éléments indépendants et réutilisables : des fonctions, regroupées en classes (où elles deviennent des méthodes), regroupées par thème dans un module (un fichier), eux-mêmes regroupés en une bibliothèque (ou package).
Certains modules font partie de la bibliothèque standard de Python ; d'autres, dits modules tiers, s'installent avec un gestionnaire de paquets comme pip.
import statistics
notes = [12, 15, 9, 18, 14]
print(statistics.mean(notes)) # 13.6Une API (Application Programming Interface) est l'ensemble des fonctions, classes et méthodes qu'une bibliothèque met à disposition pour être utilisée, sans que l'on ait besoin de connaître son implémentation interne. Utiliser une bibliothèque, c'est donc avant tout savoir lire sa documentation : quels arguments une fonction attend-elle, que renvoie-t-elle ? Une docstring est une chaîne de caractères placée en première ligne d'une fonction, d'une classe ou d'un module pour documenter son rôle ; elle est consultable avec help.
Paradigmes de programmation
Un paradigme de programmation est une façon d'organiser l'écriture d'un programme. Trois paradigmes se distinguent particulièrement :
- le paradigme impératif : le programme est une suite d'instructions qui modifient explicitement l'état de variables (affectations, boucles, conditions) ;
- le paradigme fonctionnel : on décrit le résultat voulu plutôt que la suite d'instructions pour l'obtenir ; les fonctions n'ont pas d'effets de bord (à mêmes arguments, elles renvoient toujours le même résultat) et peuvent être passées en argument à d'autres fonctions ;
- le paradigme objet : données et traitements sont réunis au sein d'une même entité, l'objet, instance d'une classe qui définit ses attributs (données) et ses méthodes (fonctions qui agissent sur ces données).
Exemple. Une même fonction, qui calcule la somme des carrés des nombres pairs d'une liste, écrite en style impératif puis en style fonctionnel :
# style imperatif : boucle et accumulateur
def somme_carres_pairs_imperatif(nombres):
total = 0
for x in nombres:
if x % 2 == 0:
total = total + x ** 2
return total
# style fonctionnel : expression de comprehension, pas de boucle explicite
def somme_carres_pairs_fonctionnel(nombres):
return sum(x ** 2 for x in nombres if x % 2 == 0)
print(somme_carres_pairs_imperatif([1, 2, 3, 4, 5, 6])) # 56
print(somme_carres_pairs_fonctionnel([1, 2, 3, 4, 5, 6])) # 56Un même langage moderne comme Python permet en général de combiner plusieurs paradigmes selon les besoins : le choix dépend du problème à résoudre.
Mise au point et débogage
Déboguer (de l'anglais bug, un défaut logiciel) un programme, c'est identifier et corriger la cause d'un comportement incorrect. Lorsque Python rencontre un problème à l'exécution, il lève une exception ; si elle n'est pas interceptée, le programme s'arrête et affiche un traceback, qui se lit de bas en haut : la dernière ligne indique le type d'erreur, les lignes précédentes retracent le chemin (la pile d'appels, voir plus bas) qui y a mené.
Exemple. Un bug fréquent : confondre une comparaison stricte (>) et une comparaison large (>=) sur une borne.
# BOGUE : la note 10 pile n'est pas comptee comme admise
def est_admis(note):
return note > 10
print(est_admis(10)) # False, probablement inattendu# CORRECTION : utiliser >= si la note 10 doit compter comme admise
def est_admis(note):
return note >= 10
print(est_admis(10)) # TrueParmi les outils de mise au point : les jeux de tests et les instructions assert, qui vérifient automatiquement qu'une fonction se comporte comme attendu ; le débogueur (debugger), qui permet de dérouler un programme pas à pas en inspectant chaque variable ; et un guide de style (comme la PEP 8 pour Python), qui réduit le risque d'introduire des bugs en rendant le code plus facile à relire.
Récursivité
Fonction récursive, cas de base et appel récursif
Une fonction est dite récursive lorsqu'elle s'appelle elle-même dans son propre corps. Elle comporte toujours deux ingrédients :
- un ou plusieurs cas de base : des valeurs de l'argument pour lesquelles le résultat est connu directement, sans appel récursif ;
- un appel récursif : un appel de la fonction à elle-même, sur un problème de taille strictement plus petite, qui se rapproche du cas de base.
Exemple. La factorielle : et pour .
def factorielle(n):
if n == 0: # cas de base
return 1
return n * factorielle(n - 1) # appel recursifLa pile d'appels
Lorsqu'une fonction récursive s'appelle elle-même, chaque appel « en attente » (qui n'a pas encore reçu son résultat) est stocké dans une structure appelée pile d'appels (call stack). Une pile fonctionne comme une pile d'assiettes : on empile un élément, ou on dépile le dernier élément posé, mais on ne peut pas accéder directement à un élément qui n'est pas au sommet.
Exemple, tracé variable par variable. Déroulons factorielle(4). Chaque appel s'empile, en attendant le résultat de l'appel suivant :
factorielle(4) attend 4 * factorielle(3)
factorielle(3) attend 3 * factorielle(2)
factorielle(2) attend 2 * factorielle(1)
factorielle(1) attend 1 * factorielle(0)
factorielle(0) renvoie 1 <- cas de base atteintPuis on dépile en remontant, chaque appel en attente calculant à son tour son résultat :
factorielle(0) -> 1
factorielle(1) -> 1 * 1 = 1
factorielle(2) -> 2 * 1 = 2
factorielle(3) -> 3 * 2 = 6
factorielle(4) -> 4 * 6 = 24Attention au débordement de pile. La pile d'appels a une taille limitée : au-delà d'un certain nombre d'appels récursifs imbriqués (1000 par défaut en Python), une erreur RecursionError est levée. Une fonction récursive doit donc toujours progresser vers son cas de base, sans quoi les appels s'empilent indéfiniment.
Méthode pour écrire une fonction récursive.
- Déterminer le type de la valeur renvoyée.
- Identifier le ou les cas de base.
- Déterminer comment la taille du problème diminue à chaque appel, pour garantir qu'on atteindra le cas de base.
- Écrire l'appel récursif, en veillant à ce qu'il renvoie un résultat du même type que le cas de base.
Calculabilité et décidabilité
Un programme est aussi une donnée
Un programme informatique n'est pas fondamentalement différent des données qu'il manipule : du point de vue de la machine, un fichier source ou un fichier exécutable ne sont que des suites de bits. Cette absence de différence de nature entre code et donnée explique la puissance de nombreux outils : un interpréteur prend un programme en entrée pour l'exécuter, un lanceur de tests prend des programmes en entrée pour les tester, un antivirus scanne des programmes à la recherche de séquences suspectes.
Un problème est dit calculable s'il existe un algorithme qui le résout en un nombre fini d'étapes. La thèse de Church-Turing affirme que toutes les notions raisonnables d'algorithme (machines de Turing, programmes Python, Java, ou tout autre langage) calculent exactement les mêmes fonctions : la calculabilité d'un problème ne dépend donc pas du langage de programmation choisi pour l'exprimer.
Le problème de l'arrêt
Un problème de décision (dont la réponse est oui ou non) est dit décidable s'il existe un algorithme qui le résout et qui s'arrête toujours en donnant la bonne réponse. Le problème de l'arrêt demande : existe-t-il un programme qui, à partir du code source d'un programme quelconque et d'une entrée, détermine toujours correctement si ce programme s'arrête sur cette entrée ?
La réponse est non : le problème de l'arrêt est indécidable. On le démontre par l'absurde, en supposant qu'une fonction arret(prog, x) existe, qui termine toujours et renvoie True si prog s'arrête sur l'entrée x, False sinon. On construit alors une fonction qui s'appelle elle-même en argument :
def diag(entree):
if arret(entree, entree):
while True:
pass # boucle infinie
else:
return TrueEn évaluant diag(diag), on obtient une contradiction dans les deux cas possibles : si arret(diag, diag) renvoie True (donc diag(diag) est censé s'arrêter), alors diag entre dans la boucle infinie, donc diag(diag) ne s'arrête pas ; si arret(diag, diag) renvoie False (donc diag(diag) est censé ne pas s'arrêter), alors diag renvoie True, donc diag(diag) s'arrête. L'hypothèse de départ — l'existence de la fonction arret — est donc fausse.
Ce résultat, démontré indépendamment par Alan Turing et Alonzo Church, ne dépend d'aucun langage de programmation particulier : c'est une conséquence de la thèse de Church-Turing, comme la calculabilité elle-même.