Fonctions, spécification et mise au point
Première · NSI · Langages et programmation
Langages et programmation
Définir, appeler et comprendre une fonction
Définir une fonction
Une fonction est un bloc de code nommé, écrit une fois et réutilisable autant de fois qu'on veut. Elle évite les copier-coller, découpe un gros problème en morceaux testables séparément, et rend le programme lisible.
def aire_rectangle(longueur, largeur):
return longueur * largeur
La ligne def ne fait que définir la fonction : rien ne s'exécute tant qu'on ne l'appelle pas.
a = aire_rectangle(3, 5) # a vaut 15
return n'est pas print
C'est la confusion numéro un de l'année. print affiche un texte à l'écran, pour un humain ; return renvoie une valeur au programme, pour la machine. Une fonction qui se contente d'afficher ne sert à rien dans un calcul !
Autre point capital : return met immédiatement fin à la fonction. Toute instruction placée après lui, dans le même chemin d'exécution, n'est jamais exécutée. Et une fonction qui n'exécute aucun return renvoie automatiquement la valeur spéciale None.
La portée des variables
Une variable créée à l'intérieur d'une fonction est locale : elle naît à l'appel, vit le temps de l'exécution, puis disparaît. Elle est invisible depuis l'extérieur.
def calcul():
resultat = 42
return resultat
calcul()
print(resultat) # NameError : resultat n'existe pas ici
Une variable définie hors de toute fonction est globale : les fonctions peuvent la lire, mais si elles lui affectent une valeur, Python crée en réalité une nouvelle variable locale du même nom, et la globale reste intacte. Cette étanchéité est une bonne nouvelle : elle garantit qu'une fonction ne peut pas saboter en douce le reste du programme. La bonne pratique consiste donc à tout faire passer par les paramètres et le return.
Langages et programmation
Spécifier, tester et déboguer
Spécifier avant de coder
Spécifier une fonction, c'est décrire ce qu'elle fait — pas comment elle le fait. En Python, cette description se place dans une docstring : une chaîne entre triples guillemets, juste après la ligne def.
def moyenne(notes):
'''Renvoie la moyenne des notes.
notes : liste non vide de nombres
Renvoie : un float compris entre min(notes) et max(notes)'''
return sum(notes) / len(notes)
La docstring n'est pas un commentaire décoratif : c'est le contrat de la fonction. Elle indique à qui l'utilise ce qu'il doit fournir et ce qu'il obtiendra en retour, sans avoir à lire une seule ligne du code.
Les trois familles d'erreurs
| Famille | Quand est-elle détectée ? | Exemples | Comment la corriger |
|---|---|---|---|
| Erreur de syntaxe | Avant toute exécution : rien ne démarre | Deux-points oubliés, parenthèse non fermée, indentation incohérente | Lire le numéro de ligne signalé — l'erreur est souvent juste AVANT |
| Erreur d'exécution | Pendant l'exécution : le programme s'arrête net | ZeroDivisionError, NameError, TypeError, IndexError | Lire le message et la trace d'appels, qui nomment la ligne fautive |
| Erreur sémantique (logique) | Jamais automatiquement : le programme tourne et donne un résultat FAUX | Une somme au lieu d'une moyenne, un range(n) au lieu de range(1, n+1) | Jeu de tests, affichages intermédiaires, exécution pas à pas |
Exercices corrigés
Que se passe-t-il quand l'instruction assert x > 0 est exécutée alors que x vaut −3 ?
- A.Rien, l'exécution continue normalement
- B.Le programme s'arrête sur une AssertionErrorBonne réponse
- C.Python remplace automatiquement x par sa valeur absolue
- D.Un avertissement s'affiche mais le programme continue
Réponse : B — Le programme s'arrête sur une AssertionError
assert vérifie une condition : si elle est vraie, l'exécution se poursuit sans rien signaler ; si elle est fausse, le programme s'interrompt immédiatement en levant une AssertionError. C'est tout l'intérêt : échouer tôt, à l'endroit exact du problème, plutôt que de laisser un résultat faux se propager. Un assert ne corrige jamais rien de lui-même.
Qu'est-ce qu'une précondition ?
- A.Une propriété qui doit être vraie sur les arguments avant l'appel de la fonctionBonne réponse
- B.Une propriété garantie sur le résultat après l'exécution
- C.La première ligne de code exécutée dans la fonction
- D.Un test qu'on écrit après avoir fini de programmer
Réponse : A — Une propriété qui doit être vraie sur les arguments avant l'appel de la fonction
La précondition porte sur ce qui ENTRE dans la fonction et doit être respectée par celui qui l'appelle : « la liste est non vide », « n est un entier positif ». La propriété garantie sur ce qui SORT est la postcondition, et c'est l'auteur de la fonction qui en répond. Retiens la logique du contrat : si l'appelant tient sa part (précondition), l'auteur tient la sienne (postcondition).
Le code contient l'instruction resultat = total / effectif, et effectif vaut 0. Que se passe-t-il ?
- A.Le programme s'arrête sur une erreur d'exécution ZeroDivisionErrorBonne réponse
- B.resultat vaut 0, Python remplaçant silencieusement la division impossible
- C.resultat vaut None, comme pour toute opération invalide en Python
- D.Python signale une erreur de syntaxe au chargement du fichier
Réponse : A — Le programme s'arrête sur une erreur d'exécution ZeroDivisionError
La ligne est syntaxiquement irréprochable : Python l'accepte sans broncher au chargement. Le problème n'apparaît qu'au moment où elle est exécutée AVEC effectif à 0, et se manifeste par une ZeroDivisionError qui interrompt le programme. C'est le prototype de l'erreur d'exécution : elle dépend des données, pas du texte du programme. Une précondition assert effectif > 0 permettrait de la signaler plus tôt et plus clairement.
Une fonction plus_grand(a, b) est censée renvoyer le plus grand des deux nombres, mais elle renvoie en réalité toujours a. Quel test la démasque ?
- A.assert plus_grand(9, 3) == 9
- B.assert plus_grand(3, 9) == 9Bonne réponse
- C.assert plus_grand(5, 5) == 5
- D.assert plus_grand(7, 2) == 7
Réponse : B — assert plus_grand(3, 9) == 9
Il faut construire un cas où la bonne réponse n'est PAS a. Avec plus_grand(3, 9), la fonction fautive renvoie 3 alors qu'on attend 9 : le test échoue et le bug apparaît. Les trois autres tests passent tous, puisqu'ils placent le maximum en première position ou utilisent deux valeurs égales. Morale de l'histoire : un test qui passe ne prouve rien s'il n'a pas été choisi pour pouvoir échouer.
On écrit compteur = 0 au niveau global, puis une fonction def incremente() : print(compteur) ; compteur = compteur + 1. Que se passe-t-il à l'appel de incremente() ?
- A.Le programme affiche 0 puis compteur vaut 1
- B.Le programme affiche 0 mais compteur reste à 0
- C.Python lève une UnboundLocalError dès le printBonne réponse
- D.Python lève une erreur de syntaxe au chargement
Réponse : C — Python lève une UnboundLocalError dès le print
Python analyse le corps de la fonction AVANT de l'exécuter : comme il y trouve une affectation à compteur, il décide que compteur sera une variable locale pour tout le corps de la fonction, y compris avant cette ligne. Le print tente donc de lire une variable locale à laquelle rien n'a encore été affecté : c'est l'UnboundLocalError. Attention à la subtilité — une fonction peut LIRE une globale sans problème, mais dès qu'elle lui affecte quelque chose quelque part, le nom devient local partout dans la fonction. La bonne pratique reste de passer par un paramètre et un return.
On écrit def double_liste(t) : for i in range(len(t)) : t[i] = 2 * t[i] — sans return. Puis notes = [1, 2] et u = double_liste(notes). Que valent u et notes ?
- A.u = [2, 4] et notes = [1, 2]
- B.u = [2, 4] et notes = [2, 4]
- C.u = None et notes = [1, 2]
- D.u = None et notes = [2, 4]Bonne réponse
Réponse : D — u = None et notes = [2, 4]
Il faut raisonner sur deux plans à la fois. D'abord la valeur renvoyée : la fonction ne contient aucun return, donc elle renvoie None et u vaut None. Ensuite l'effet de bord : une liste est un objet MUTABLE, et le paramètre t désigne la même liste que notes — pas une copie. Les affectations t[i] = 2 * t[i] modifient donc réellement notes, qui devient [2, 4]. C'est exactement le comportement d'un tri « en place ». Retiens la règle : modifier le CONTENU d'une liste reçue en paramètre affecte l'appelant, mais réaffecter le paramètre lui-même (t = [...]) ne changerait rien à l'extérieur.
Quel mot-clé permet de définir une fonction en Python ?
- A.function
- B.defBonne réponse
- C.fonction
- D.return
Réponse : B — def
En Python, une définition commence par def, suivi du nom de la fonction, de ses paramètres entre parenthèses et de deux-points : def ma_fonction(x) :. Le mot function existe en JavaScript, pas en Python. Quant à return, il sert à renvoyer une valeur DEPUIS l'intérieur d'une fonction, pas à la définir.
Que renvoie une fonction dont le corps ne contient aucune instruction return ?
- A.0
- B.La chaîne vide
- C.Rien du tout, l'appel provoque une erreur
- D.NoneBonne réponse
Réponse : D — None
Toute fonction Python renvoie une valeur. Si aucun return n'est exécuté, cette valeur est None, un objet spécial signifiant « pas de valeur utile ». Ce n'est ni 0 ni la chaîne vide, et il n'y a aucune erreur au moment de l'appel — l'erreur viendra plus tard, quand on essaiera d'utiliser ce None dans un calcul, avec une TypeError.
Dans def aire(longueur, largeur) : ... appelée par aire(3, 5), qu'est-ce que « longueur » ?
- A.Un paramètreBonne réponse
- B.Un argument
- C.Une variable globale
- D.Une valeur de retour
Réponse : A — Un paramètre
Le vocabulaire est précis : les noms qui figurent dans la DÉFINITION sont les paramètres (longueur, largeur), et les valeurs fournies lors de l'APPEL sont les arguments (3 et 5). Un paramètre est une variable locale à la fonction, jamais une variable globale, et il n'a rien à voir avec la valeur de retour.
À quoi sert la docstring d'une fonction ?
- A.À accélérer l'exécution de la fonction
- B.À déclarer le type des paramètres, obligatoire en Python
- C.À spécifier ce que fait la fonction : ses préconditions et ce qu'elle renvoieBonne réponse
- D.À exécuter automatiquement les tests de la fonction
Réponse : C — À spécifier ce que fait la fonction : ses préconditions et ce qu'elle renvoie
La docstring est le contrat de la fonction : elle décrit CE QU'ELLE FAIT, ce qu'on doit lui fournir (préconditions) et ce qu'elle garantit en retour (postcondition). Elle n'a aucun effet sur la vitesse, et Python n'oblige à déclarer aucun type puisqu'il est à typage dynamique. Elle n'exécute rien non plus : les tests sont des assertions, écrites à part.
On définit def aire(longueur, largeur=2) : return longueur * largeur. Que vaut aire(5) ?
- A.5
- B.Une erreur : il manque un argument
- C.10Bonne réponse
- D.7
Réponse : C — 10
Le paramètre largeur possède une valeur par défaut : quand l'appel ne fournit qu'un seul argument, largeur prend automatiquement la valeur 2. On calcule donc 5 × 2 = 10. Il n'y a pas d'erreur, précisément parce que le paramètre manquant a une valeur par défaut. Répondre 7 revient à additionner au lieu de multiplier.
On écrit def triple(x) : print(3 * x), puis y = triple(4). Que vaut y ?
- A.12
- B.'12'
- C.4
- D.NoneBonne réponse
Réponse : D — None
La fonction AFFICHE 12 à l'écran, mais elle ne RENVOIE rien : aucune instruction return. Elle renvoie donc None, et c'est cette valeur qui est rangée dans y. C'est la confusion la plus coûteuse de l'année : y + 1 déclencherait une TypeError. Pour que y vaille 12, il faudrait écrire return 3 * x à la place du print.
S'entraîner sur ce chapitre
Ce chapitre compte 38 exercices corrigés dans l'application, avec le suivi de ta progression.
Commencer les exercices