Paradigmes de programmation et mise au point
Terminale · NSI · Langages et programmation
Langages et programmation
Trois façons de programmer : impératif, objet, fonctionnel
Un même problème peut être résolu de plusieurs manières, non pas parce qu'on change de langage, mais parce qu'on change de façon de penser le programme. C'est ce qu'on appelle un paradigme de programmation. Python a la particularité d'en accepter plusieurs : c'est un langage multiparadigme. Savoir lequel tu utilises, et pourquoi, fait partie des attendus du programme de Terminale.
Le paradigme impératif
C'est celui que tu pratiques depuis la Seconde. Le programme est une suite d'instructions exécutées dans l'ordre, qui modifient progressivement l'état de la machine. Ses outils sont l'affectation de variables, les conditions et les boucles. On décrit comment obtenir le résultat, étape par étape.
somme = 0
for k in range(1, 11):
somme = somme + k
print(somme)
Ici, la variable somme change de valeur dix fois : l'état évolue au fil de l'exécution. C'est la signature du style impératif.
Le paradigme objet. Le programme est organisé autour d'objets qui regroupent des données (les attributs) et les opérations qui agissent sur elles (les méthodes). Une classe est le moule, l'instance est l'objet fabriqué à partir de ce moule. La méthode __init__, appelée automatiquement à la création, initialise les attributs, et le premier paramètre self désigne l'instance elle-même.
class CompteBancaire:
def __init__(self, solde):
self.solde = solde
def deposer(self, montant):
self.solde = self.solde + montant
c = CompteBancaire(100)
c.deposer(50)
L'intérêt majeur est l'encapsulation : le reste du programme manipule le compte par ses méthodes, sans avoir à connaître la façon dont le solde est stocké. On peut donc changer l'intérieur de la classe sans casser le code qui l'utilise.
Le paradigme fonctionnel. Le programme est enfin vu comme une composition de fonctions, au sens mathématique du terme. On privilégie les fonctions pures, on évite les variables mutables et les effets de bord, et on remplace volontiers les boucles par la récursivité. On décrit quoi calculer plutôt que comment le calculer pas à pas.
| Impératif | Objet | Fonctionnel | |
|---|---|---|---|
| Unité de base | L'instruction | L'objet (attributs + méthodes) | La fonction |
| Gestion de l'état | Variables modifiées au fil du temps | État interne encapsulé dans l'objet | État évité : on préfère l'immuabilité |
| Répétition | Boucles for et while | Boucles, souvent dans les méthodes | Récursivité |
| Effets de bord | Fréquents et assumés | Contrôlés, limités à l'objet | Bannis autant que possible |
| Point fort | Simple, proche du fonctionnement machine | Modélise bien les entités d'un problème | Se teste et se raisonne facilement |
| Exemple d'usage | Algorithme de tri, parcours de tableau | Interface graphique, simulation | Transformation de données, calcul mathématique |
Langages et programmation
Mettre au point un programme : spécification, assertions et tests
Écrire un programme qui fonctionne du premier coup n'arrive à personne. La mise au point n'est donc pas une punition infligée aux mauvais programmeurs : c'est une étape normale du travail, avec ses méthodes et ses outils. L'objectif n'est pas seulement de faire disparaître les erreurs, mais de gagner de bonnes raisons de croire que le programme est correct.
Trois familles d'erreurs se distinguent, et elles ne se repèrent pas du tout de la même façon.
| Type d'erreur | Quand elle se manifeste | Exemple | Difficulté |
|---|---|---|---|
| Erreur de syntaxe | Avant même l'exécution : le code n'est pas un texte Python valide | Oubli des deux-points après un if, parenthèse non fermée | Facile : signalée avec le numéro de ligne |
| Erreur d'exécution | Pendant l'exécution : le programme s'interrompt brutalement | Division par zéro, indice hors des bornes d'une liste | Moyenne : le message d'erreur oriente la recherche |
| Erreur de logique | Jamais signalée : le programme tourne, mais le résultat est faux | Une boucle qui s'arrête un tour trop tôt, un signe inversé | Difficile : rien ne prévient, seuls les tests la révèlent |
L'erreur de logique est la plus dangereuse, précisément parce qu'elle est silencieuse. Un programme qui plante t'avertit ; un programme qui répond faux te laisse croire que tout va bien.
Spécifier avant de coder. La spécification décrit ce que la fonction doit faire, indépendamment de la manière dont elle le fait : quels arguments elle attend, sous quelles conditions, et ce qu'elle renvoie. On l'écrit en Python dans une docstring, entre triples guillemets, juste après la ligne def. On y précise les préconditions (ce qui doit être vrai à l'entrée) et la postcondition (ce qui est garanti en sortie).
Construire un bon jeu de tests. Un jeu de tests n'est utile que s'il est varié. Tester trois fois le même cas ordinaire ne prouve rien. Un jeu sérieux comporte au minimum : des cas ordinaires, des cas limites (liste vide, valeur nulle, premier et dernier élément, valeur unique), et des cas interdits pour vérifier que les préconditions sont bien contrôlées.
Invariant et variant de boucle. Tester, c'est constater sur des exemples ; prouver, c'est établir la correction pour tous les cas. Deux outils y contribuent. L'invariant de boucle est une propriété vraie avant la boucle, préservée par chaque tour, et qui donne le résultat voulu à la sortie : il démontre la correction. Le variant de boucle est une quantité entière positive qui décroît strictement à chaque tour : comme elle ne peut décroître indéfiniment en restant positive, la boucle s'arrête. Il démontre la terminaison.
Exercices corrigés
Qu'est-ce qui caractérise le paradigme impératif ?
- A.Le programme est une composition de fonctions sans état modifiable
- B.Le programme est une suite d'instructions qui modifient l'état des variablesBonne réponse
- C.Le programme est un ensemble d'objets qui s'échangent des messages
- D.Le programme décrit uniquement le résultat voulu, sans indiquer comment l'obtenir
Réponse : B — Le programme est une suite d'instructions qui modifient l'état des variables
Le style impératif décrit une succession d'étapes qui font évoluer l'état de la machine : affectations, conditions, boucles. C'est celui que tu utilises depuis tes débuts en Python. L'option 1 décrit le paradigme fonctionnel, l'option 3 le paradigme objet, et l'option 4 correspond à un style déclaratif, dont SQL est un bon représentant.
On oublie les deux-points à la fin d'une ligne if. De quel type d'erreur s'agit-il ?
- A.Une erreur d'exécution
- B.Une erreur de syntaxeBonne réponse
- C.Une erreur de logique
- D.Une erreur de spécification
Réponse : B — Une erreur de syntaxe
Le texte n'est pas un programme Python valide : l'erreur est détectée avant même que la moindre instruction ne s'exécute. C'est une erreur de syntaxe, la plus facile à corriger puisque l'interpréteur indique la ligne fautive. Une erreur d'exécution surviendrait pendant le déroulement (division par zéro, indice hors bornes), et une erreur de logique ne serait jamais signalée du tout.
Que doit impérativement contenir un bon jeu de tests, en plus des cas ordinaires ?
- A.Au moins cent cas différents, pour couvrir toutes les valeurs possibles
- B.Des cas limites, comme la liste vide, l'élément unique ou les valeurs extrêmesBonne réponse
- C.Uniquement des cas où la fonction doit renvoyer une erreur
- D.Des cas tirés au hasard, pour garantir l'impartialité du test
Réponse : B — Des cas limites, comme la liste vide, l'élément unique ou les valeurs extrêmes
Ce sont les frontières qui font tomber les programmes : liste vide, tableau à un seul élément, premier et dernier indice, valeur nulle ou négative. Multiplier les cas ordinaires n'apporte presque rien — dix tests bien choisis valent mieux que cent tests redondants. Les cas interdits sont utiles aussi, mais ils ne suffisent évidemment pas à eux seuls.
Dans la définition d'une méthode Python, que représente le paramètre self ?
- A.L'instance sur laquelle la méthode est appeléeBonne réponse
- B.La classe elle-même, indépendamment de toute instance
- C.Un mot-clé réservé du langage Python, obligatoire et non modifiable
- D.La valeur que la méthode renverra à la fin de son exécution
Réponse : A — L'instance sur laquelle la méthode est appelée
Quand tu écris c.deposer(50), Python appelle en réalité la méthode en lui passant c comme premier argument : c'est ce que reçoit self. C'est pourquoi self.solde désigne l'attribut de cet objet-là précisément, et pas d'un autre. Nuance importante : self n'est pas un mot-clé réservé, seulement une convention universelle — un autre nom fonctionnerait, mais personne ne le fait.
Un programme s'exécute sans le moindre message d'erreur, mais renvoie une moyenne manifestement fausse. De quel type d'erreur s'agit-il ?
- A.Une erreur de syntaxe
- B.Une erreur d'exécution
- C.Une erreur de compilation
- D.Une erreur de logiqueBonne réponse
Réponse : D — Une erreur de logique
Rien ne plante, rien n'est signalé, mais le résultat est faux : c'est le portrait exact de l'erreur de logique. Elle vient d'un raisonnement incorrect — une borne de boucle décalée, un opérateur inversé, une division placée au mauvais endroit. C'est la plus redoutable des trois, car seule une comparaison avec un résultat calculé indépendamment peut la révéler. Les erreurs de syntaxe et d'exécution, elles, se signalent bruyamment.
Quelle caractéristique appartient au paradigme fonctionnel ?
- A.L'usage systématique de variables globales pour partager l'information
- B.La préférence pour la récursivité et l'absence d'effets de bordBonne réponse
- C.L'organisation du code en classes et en instances
- D.L'exécution des instructions strictement dans l'ordre où elles sont écrites
Réponse : B — La préférence pour la récursivité et l'absence d'effets de bord
Le style fonctionnel évite l'état modifiable : plutôt que de faire évoluer un accumulateur dans une boucle, on exprime le résultat par une définition récursive ou par composition de fonctions. Les variables globales sont exactement ce qu'il cherche à bannir, puisqu'elles créent des effets de bord. Les classes relèvent du paradigme objet, et l'exécution séquentielle du paradigme impératif.
Une fonction racine(x) exige un argument positif ou nul. Quelle est la meilleure façon de traduire cette précondition dans le code ?
- A.Ajouter un commentaire signalant que x doit être positif
- B.Renvoyer 0 lorsque x est négatif, pour éviter tout plantage
- C.Afficher un message avec print lorsque x est négatif, puis poursuivre le calcul
- D.Écrire assert x >= 0 en première ligne de la fonctionBonne réponse
Réponse : D — Écrire assert x >= 0 en première ligne de la fonction
L'assertion est le seul dispositif qui garantit un arrêt immédiat et lisible en cas de violation. Un commentaire n'est pas exécuté et n'empêche donc rien. Renvoyer 0 est nettement pire : cela transforme une erreur bruyante en erreur silencieuse, qui se propagera dans tout le programme — exactement le contraire de ce qu'on cherche. Afficher un message puis continuer laisse aussi le calcul se poursuivre sur une base fausse.
Pour une boucle while n > 1, avec n divisé par 2 à chaque tour à l'aide de la division entière, quel variant de boucle prouve la terminaison ?
- A.Le nombre de tours déjà effectués, qui augmente à chaque itération
- B.La valeur de la condition n > 1, qui finit par devenir fausse
- C.La valeur de n elle-même : c'est un entier positif qui décroît strictement à chaque tourBonne réponse
- D.La valeur 2, qui est le diviseur constant utilisé dans la boucle
Réponse : C — La valeur de n elle-même : c'est un entier positif qui décroît strictement à chaque tour
Un variant doit être un entier positif qui décroît strictement à chaque itération : comme un entier positif ne peut pas décroître indéfiniment, la boucle s'arrête forcément. Ici, n convient parfaitement, puisque n divisé par 2 est strictement inférieur à n tant que n vaut au moins 2. Le nombre de tours effectués croît au lieu de décroître, donc il ne convient pas. Une constante ne varie pas, et la condition elle-même n'est pas une quantité entière.
Dans une classe Python, quelle méthode est appelée automatiquement lors de la création d'une nouvelle instance ?
- A.self
- B.create
- C.__init__Bonne réponse
- D.new
Réponse : C — __init__
La méthode __init__ est le constructeur : elle est exécutée dès qu'on écrit par exemple c = CompteBancaire(100), et sert à initialiser les attributs de l'instance. Attention, self n'est pas une méthode : c'est le premier paramètre des méthodes, qui reçoit l'instance elle-même. Les noms create et new n'ont aucun statut particulier en Python.
À quoi sert l'instruction assert en Python ?
- A.À vérifier qu'une condition est vraie et à interrompre le programme si elle ne l'est pasBonne réponse
- B.À afficher la valeur d'une variable pour aider au débogage
- C.À corriger automatiquement une erreur détectée dans le code
- D.À déclarer le type attendu d'un paramètre de fonction
Réponse : A — À vérifier qu'une condition est vraie et à interrompre le programme si elle ne l'est pas
assert condition arrête immédiatement l'exécution si la condition est fausse, avec un message explicite si on en a fourni un. L'idée est qu'un arrêt net vaut mieux qu'un résultat faux propagé silencieusement dans tout le programme. Pour afficher une valeur, c'est print. Et aucune instruction Python ne corrige un bug toute seule.
Qu'est-ce qu'une fonction pure ?
- A.Une fonction qui ne contient aucune boucle
- B.Une fonction qui n'utilise que des nombres entiers
- C.Une fonction écrite sans utiliser de bibliothèque extérieure
- D.Une fonction qui renvoie toujours le même résultat pour les mêmes arguments et ne modifie rien à l'extérieur d'elle-mêmeBonne réponse
Réponse : D — Une fonction qui renvoie toujours le même résultat pour les mêmes arguments et ne modifie rien à l'extérieur d'elle-même
Les deux critères sont indissociables : le déterminisme (mêmes entrées, même sortie) et l'absence d'effet de bord. Une fonction pure peut parfaitement contenir une boucle ou manipuler des chaînes de caractères : ce qui compte, c'est qu'elle ne touche à rien d'extérieur — ni variable globale, ni liste reçue en paramètre, ni affichage, ni fichier.
Qu'est-ce qu'un test unitaire ?
- A.Un test qui vérifie que le programme complet fonctionne du début à la fin
- B.Un test qui mesure le temps d'exécution d'un algorithme
- C.Un test qui vérifie le comportement d'une seule fonction sur un cas précis, en comparant le résultat obtenu au résultat attenduBonne réponse
- D.Un test réalisé par un utilisateur qui essaie le logiciel
Réponse : C — Un test qui vérifie le comportement d'une seule fonction sur un cas précis, en comparant le résultat obtenu au résultat attendu
Unitaire signifie qu'on isole une unité de code — en pratique une fonction — pour la vérifier seule. On lui donne des entrées connues et on compare sa sortie à ce qu'on a calculé indépendamment, souvent avec un assert. Tester le programme entier relève des tests d'intégration, et mesurer un temps d'exécution relève de l'analyse de complexité, pas des tests.
S'entraîner sur ce chapitre
Ce chapitre compte 37 exercices corrigés dans l'application, avec le suivi de ta progression.
Commencer les exercices