Programmation : récursivité, modularité, paradigmes et mise au point : fiche de révision
Fonctions récursives et pile d'appels, modules et bibliothèques, paradigmes impératif, fonctionnel et objet, tests, assertions et gestion des erreurs : la fiche complète.
1La récursivité
Une fonction est récursive si elle s'appelle elle-même. Elle doit comporter un cas de base (qui ne fait pas d'appel récursif) et un cas récursif qui se rapproche du cas de base ; sinon, les appels s'empilent indéfiniment jusqu'à l'erreur RecursionError (dépassement de la pile d'appels, limitée à environ 1000 en Python).
def factorielle(n):
if n == 0: return 1
return n * factorielle(n - 1)
Chaque appel crée un cadre sur la pile d'appels avec ses propres variables ; factorielle(3) empile factorielle(2), puis factorielle(1), puis factorielle(0) qui renvoie 1, et les résultats se dépilent : 1, 1, 2, 6. La terminaison se justifie par une quantité entière positive qui décroît strictement à chaque appel (ici n).
Récursif ou itératif ? Tout programme récursif a un équivalent itératif (avec une pile explicite au besoin). Le récursif est naturel sur les structures récursives (arbres, listes chaînées) et les algorithmes « diviser pour régner » ; l'itératif évite le coût de la pile. Piège classique : la suite de Fibonacci naïve fait un nombre exponentiel d'appels. La mémoïsation (dictionnaire des résultats déjà calculés) ramène à un temps linéaire.
2Modularité, API et bibliothèques
Un module est un fichier Python regroupant des fonctions et des classes réutilisables ; on l'importe avec import module ou from module import nom. Une bibliothèque est un ensemble de modules (math, random, csv, json, matplotlib…). L'interface de programmation (API) d'un module est l'ensemble de ce qu'il expose : noms des fonctions, paramètres, valeurs de retour, documentés dans les docstrings.
Bonnes pratiques attendues : une fonction = une tâche, un nom explicite, une docstring qui indique le rôle, les paramètres et le résultat, pas de variable globale modifiée en cachette. La modularité permet de tester chaque fonction indépendamment et de réutiliser le code.
Utiliser une API signifie lire sa documentation, pas son code : on appelle math.sqrt(2) sans connaître l'algorithme. Au bac, on peut vous donner la documentation d'une fonction inconnue et vous demander de l'utiliser correctement.
3Paradigmes : impératif, fonctionnel, objet
Le paradigme impératif décrit une suite d'instructions qui modifient un état (variables, boucles, affectations) : c'est le style de base de Python. Le paradigme fonctionnel privilégie les fonctions sans effet de bord (elles renvoient un résultat sans modifier l'extérieur), la composition, les fonctions passées en argument (map, filter, sorted avec key) et la récursivité ; il facilite le raisonnement et les tests. Le paradigme objet organise le programme en classes qui regroupent données (attributs) et comportements (méthodes) ; les objets sont des instances ; l'encapsulation cache les détails internes derrière une interface.
class Compte:
def __init__(self, solde=0): self.solde = solde
def deposer(self, montant): self.solde += montant
Python est multi-paradigme : un même programme mélange souvent les trois. Au bac, on demande de reconnaître le paradigme d'un extrait, d'écrire une petite classe, ou de transformer une boucle en écriture fonctionnelle (par exemple une compréhension de liste ou un appel à sorted avec une fonction clé).
4Mise au point : tests, assertions, exceptions
Les bugs sont inévitables ; la mise au point est une compétence évaluée. Les assertions vérifient une condition et lèvent AssertionError sinon : assert n >= 0, 'n doit être positif'. Elles sont utiles comme préconditions (sur les entrées) et postconditions (sur le résultat). Les tests unitaires vérifient une fonction sur des cas choisis, y compris les cas limites (liste vide, zéro, valeur négative) ; en Python, une série d'assert dans une fonction test_… ou le module doctest (exemples dans la docstring).
Les exceptions signalent une erreur à l'exécution : ZeroDivisionError, IndexError, KeyError, TypeError, ValueError. On les gère avec try/except pour traiter le cas sans arrêter le programme, et on peut en lever avec raise ValueError('message').
Méthode de débogage : reproduire le bug sur un cas minimal, afficher les valeurs intermédiaires (print ou débogueur), formuler une hypothèse, corriger, relancer les tests. Les erreurs classiques : indice hors limites (off-by-one), variable non initialisée, mutation involontaire d'une liste passée en argument, comparaison de flottants avec ==.
Définitions à connaître par cœur
Quiz : teste-toi sur programmation : récursivité, modularité, paradigmes et mise au point
8 questions corrigées. Réponds avant d'ouvrir la correction !
1. Que se passe-t-il si une fonction récursive n'a pas de cas de base atteignable ?
- A.Elle renvoie None
- B.Elle boucle jusqu'à RecursionError (pile d'appels saturée)
- C.Python la transforme en boucle
- D.Elle renvoie 0
Voir la réponse
Réponse : B. Elle boucle jusqu'à RecursionError (pile d'appels saturée)
Les appels s'empilent sans fin ; Python arrête avec RecursionError vers 1000 appels imbriqués.
2. Pourquoi la version naïve de Fibonacci (fib(n-1) + fib(n-2)) est-elle lente ?
- A.Parce que Python est lent
- B.Parce que les mêmes valeurs sont recalculées un nombre exponentiel de fois
- C.Parce qu'elle utilise trop de mémoire
- D.Parce qu'elle n'a pas de cas de base
Voir la réponse
Réponse : B. Parce que les mêmes valeurs sont recalculées un nombre exponentiel de fois
fib(5) appelle fib(3) deux fois, fib(2) trois fois… La mémoïsation évite ces recalculs.
3. Quel paradigme repose sur des fonctions sans effet de bord ?
- A.Impératif
- B.Fonctionnel
- C.Objet
- D.Procédural
Voir la réponse
Réponse : B. Fonctionnel
Le paradigme fonctionnel privilégie les fonctions pures, qui ne modifient rien en dehors de leur résultat.
4. Que fait « assert x > 0 » si x vaut −1 ?
- A.Rien
- B.Affiche un avertissement
- C.Lève AssertionError et interrompt le programme
- D.Met x à 0
Voir la réponse
Réponse : C. Lève AssertionError et interrompt le programme
L'assertion échoue et lève une exception, signalant qu'une précondition n'est pas respectée.
5. Quelle exception lève liste[10] si la liste a 3 éléments ?
- A.KeyError
- B.IndexError
- C.ValueError
- D.TypeError
Voir la réponse
Réponse : B. IndexError
IndexError signale un indice hors limites ; KeyError concerne une clé absente d'un dictionnaire.
6. Qu'est-ce qu'une méthode en programmation objet ?
- A.Une variable globale
- B.Une fonction définie dans une classe et agissant sur ses instances
- C.Un module importé
- D.Un test unitaire
Voir la réponse
Réponse : B. Une fonction définie dans une classe et agissant sur ses instances
Les méthodes sont les comportements d'une classe ; self désigne l'instance sur laquelle elles agissent.
7. Comment justifier la terminaison d'une fonction récursive ?
- A.En vérifiant qu'elle compile
- B.En exhibant une quantité entière positive qui décroît strictement à chaque appel
- C.En la testant sur 5 valeurs
- D.En ajoutant un try/except
Voir la réponse
Réponse : B. En exhibant une quantité entière positive qui décroît strictement à chaque appel
Une quantité entière positive ne peut décroître indéfiniment : le cas de base est forcément atteint.
8. À quoi sert un test unitaire ?
- A.À mesurer la vitesse du programme
- B.À vérifier qu'une fonction renvoie le résultat attendu sur des cas choisis
- C.À compiler le code
- D.À documenter l'API
Voir la réponse
Réponse : B. À vérifier qu'une fonction renvoie le résultat attendu sur des cas choisis
Il compare le résultat d'une fonction à une valeur attendue, y compris sur les cas limites.
Questions fréquentes
Quand préférer une solution récursive ?
Quand le problème est lui-même récursif : arbres, listes chaînées, diviser pour régner, parcours en profondeur. Pour un simple compteur ou une somme, une boucle est plus simple et n'a pas le coût de la pile d'appels.
Qu'est-ce qu'une fonction pure ?
Une fonction dont le résultat ne dépend que de ses arguments et qui ne modifie rien à l'extérieur (pas d'effet de bord). Elle est facile à tester et à raisonner : appelée deux fois avec les mêmes arguments, elle renvoie la même chose.
Faut-il maîtriser l'héritage en programmation objet ?
Le programme de Terminale demande de savoir écrire et utiliser une classe (constructeur, attributs, méthodes) ; l'héritage n'est pas exigible mais peut apparaître dans du code à lire.
Comment se préparer à la question de mise au point ?
S'entraîner à lire un code bugué et à repérer l'erreur classique (indice, condition de boucle, cas limite oublié), et prendre l'habitude d'écrire deux ou trois assertions de test pour chaque fonction que l'on code.
