Aller au contenu
\( \definecolor{colordef}{RGB}{249,49,84} \definecolor{colorprop}{RGB}{18,102,241} \)
Éducation Nationale Française · Seconde

Python : fonctions

⌚ ~106 min ▢ 11 blocs ✓ 60 exercices PDFCours complet PDFFiche d'exercices PDFCorrections ➣ Prérequis : Python : choix et boucles
I Nommer un traitement
On emploie déjà des fonctions : print, input, int, random.randint. Chacune reçoit des valeurs entre ses parenthèses, fait son travail, et, pour la plupart, rend une valeur. Personne n'a besoin de savoir comment int est écrite pour s'en servir : son nom et ce qu'elle rend suffisent. Écrire soi-même une telle fonction, c'est ceci : un morceau de programme reçoit un nom, des paramètres, et rend une valeur avec return. Il s'écrit une fois et s'appelle autant de fois que nécessaire, et il se teste seul, appel par appel.
Signaler une erreur
Définition — Définir une fonction
Une fonction se définit par un en-tête et un corps. L'en-tête est le mot def, le nom de la fonction, ses paramètres entre parenthèses, séparés par des virgules, et un deux-points. Le corps est le bloc indenté sous l'en-tête. L'instruction return, suivie d'une expression, rend la valeur de cette expression et termine la fonction aussitôt. Définir une fonction n'exécute rien de son corps : cela donne seulement son nom à la fonction. Le corps n'est exécuté que lorsque la fonction est appelée.
Définition — Appeler une fonction
Un appel est le nom de la fonction suivi de ses arguments entre parenthèses. Chaque paramètre reçoit l'argument écrit à la même position, le corps s'exécute, et l'appel prend la valeur rendue par return : un appel est une expression, utilisable partout où une valeur est attendue, dans un print, dans une affectation, dans une condition. La définition doit avoir été exécutée avant le premier appel : par convention, les définitions s'écrivent en tête du programme.
Définition — L'appel et sa valeur
Une fonction se teste en écrivant des appels et les valeurs qu'ils doivent donner. Par convention, un appel s'écrit sur une ligne commençant par >>>, comme dans la console Python, et sa valeur sur la ligne suivante, écrite comme Python l'écrit : une chaîne entre guillemets, un nombre décimal avec son point, True ou False pour une comparaison. Une fonction qui se termine sans return rend la valeur spéciale None. La vraie console Python n'affiche pas None ; un test l'écrit quand même, pour que chaque appel ait sa valeur sur la ligne suivante. Quand l'appel affiche quelque chose, les lignes affichées viennent d'abord, puis la valeur ; quand l'appel échoue, seule la dernière ligne de l'erreur est écrite.
Exemple
Quelle est la valeur de chaque appel ?
def is_even(n):
    return n % 2 == 0
>>> is_even(4)
>>> is_even(7)
>>> is_even(0)

>>> is_even(4)
True
>>> is_even(7)
False
>>> is_even(0)
True
À chaque appel, le paramètre n reçoit l'argument, la comparaison est calculée et return rend sa valeur, True ou False. La fonction est écrite une fois et appelée trois fois avec trois valeurs différentes. Son nom est une question, et sa valeur y répond.

Exemple
Quelle est la valeur de chaque appel ?
def greet(name):
    return "Hello " + name
>>> greet("Ada")
>>> greet("Ada") + "!"
>>> print(greet("Ada"))

>>> greet("Ada")
'Hello Ada'
>>> greet("Ada") + "!"
'Hello Ada!'
>>> print(greet("Ada"))
Hello Ada
None
La valeur du premier appel est une chaîne, écrite entre guillemets parce que c'est une valeur, pas un affichage. Le deuxième appel montre qu'un appel est une expression : la chaîne rendue est collée à "!". La troisième ligne affiche la chaîne, sans guillemets, puis montre la valeur de la ligne entière : print ne rend rien, donc sa valeur est None.

Méthode — En-tête et corps puis deux appels
  1. Écrire l'en-tête : un nom qui dit ce que fait la fonction, un verbe ou une question comme is_even ; un paramètre par valeur dont elle a besoin ; et, en mots, ce qu'elle rend.
  2. Écrire le corps : calculer à partir des paramètres seulement, et finir par return suivi du résultat.
  3. Écrire deux appels avec leurs valeurs attendues, puis les exécuter. Une fonction n'est jamais terminée avant d'avoir été appelée.
Exemple
Écrire une fonction max_of_two(a, b) qui rend le plus grand de deux nombres, et la tester.

En-tête : le nom dit ce qui revient ; deux paramètres, un par nombre ; la fonction rend un nombre. Corps : un if avec un return par cas. Trois appels : un par cas, et un sur la frontière.
def max_of_two(a, b):
    if a >= b:
        return a
    else:
        return b

print(max_of_two(3, 8))
print(max_of_two(10, 2))
print(max_of_two(5, 5))
Le programme affiche 8, 10 puis 5 : le troisième appel vérifie le cas de deux nombres égaux, la frontière du >=. Chaque return termine la fonction : quand a >= b vaut True, la valeur a revient et le else n'est jamais atteint. Les deux lignes print sont les appels de l'étape 3 ; dans les exercices, les appels s'écrivent comme des tests, avec >>>.

print n'est pas return
Une fonction qui affiche son résultat au lieu de le rendre a pour valeur None : elle ne peut pas servir dans un calcul.
def double(n):
    print(2 * n)
>>> double(3)
6
None
>>> double(3) + 1
6
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
Le premier appel affiche 6, puis sa valeur est None. Le second affiche 6 aussi, puis échoue : il n'y a pas de nombre auquel ajouter 1. Écrire return 2 * n, et laisser celui qui appelle décider d'afficher la valeur.
Signaler une erreur
Avant la définition et sans parenthèses
Deux erreurs fréquentes dans les premiers appels. Appeler une fonction au-dessus de sa définition arrête le programme : le nom n'existe pas encore.
print(square(3))

def square(n):
    return n * n
Traceback (most recent call last):
  File "main.py", line 1, in <module>
    print(square(3))
          ^^^^^^
NameError: name 'square' is not defined
Écrire le nom sans parenthèses n'appelle pas la fonction : la valeur de square tout seul est la fonction elle-même, pas un nombre, et square + 1 échoue avec une TypeError. Les parenthèses, même vides, sont ce qui fait un appel.
Signaler une erreur
Un return oublié
Un corps qui calcule mais n'écrit jamais return rend None, sans aucun message d'erreur.
def area(width, height):
    result = width * height
>>> area(3, 4)
None
Le produit est calculé, rangé dans result, et perdu quand la fonction se termine. La dernière ligne d'un tel corps doit être return result.
Signaler une erreur
Compétences à pratiquer
  • Prédire un appel
  • Premières fonctions
  • Fonctions à plusieurs cas
II Utiliser la valeur rendue
Un appel est une expression : sa valeur peut être affichée, mais aussi rangée, comparée, ajoutée à une autre, ou donnée à une autre fonction. C'est ce qui rend les fonctions utiles : une petite fonction écrite une fois sert dans des calculs plus grands. Trois choses en découlent : la composition des appels, le moment où return arrête la fonction, et les fonctions qui agissent au lieu de calculer.
Signaler une erreur
Définition — Composer des appels
La valeur d'un appel revient exactement là où l'appel est écrit. Un appel peut donc apparaître dans une expression, dans une condition, comme argument d'un autre appel, ou dans le corps d'une boucle, où il est recalculé à chaque tour. Une fonction peut appeler une autre fonction, pourvu que les deux soient définies avant que l'appel ne s'exécute.
Définition — return termine la fonction
L'instruction return termine la fonction aussitôt : les lignes qui la suivent dans le même bloc ne sont jamais exécutées. Un corps peut contenir plusieurs return, un par cas, dans les branches d'un if. Un return dans une boucle quitte la boucle et la fonction ensemble, au tour où il est atteint.
Définition — Les procédures
Une fonction sans return, ou avec un return seul, s'appelle une procédure : elle agit, par exemple elle affiche quelque chose, et sa valeur est None. Une procédure s'appelle comme une instruction, sur une ligne à elle : dans une expression, elle n'apporterait que sa valeur None. En règle générale, une fonction qui calcule rend son résultat et n'affiche rien, et une procédure qui affiche calcule le moins possible.
Exemple
Quelle est la valeur de chaque appel ?
def count_multiples(n, k):
    count = 0
    for i in range(1, n + 1):
        if i % k == 0:
            count = count + 1
    return count
>>> count_multiples(10, 3)
>>> count_multiples(10, 20)
>>> count_multiples(10, 3) + count_multiples(10, 5)

>>> count_multiples(10, 3)
3
>>> count_multiples(10, 20)
0
>>> count_multiples(10, 3) + count_multiples(10, 5)
5
Le corps est le schéma du compteur, et return vient après la boucle, une fois le compte terminé. Les multiples de 3 jusqu'à 10 sont 3, 6 et 9 ; il n'y a aucun multiple de 20 entre 1 et 10. La troisième ligne additionne les valeurs de deux appels : 3 + 2.

Exemple
Quelle est la valeur de chaque appel ?
def is_prime(n):
    if n < 2:
        return False
    for d in range(2, n):
        if n % d == 0:
            return False
    return True
>>> is_prime(7)
>>> is_prime(12)
>>> is_prime(1)

>>> is_prime(7)
True
>>> is_prime(12)
False
>>> is_prime(1)
False
Trois sorties du corps. Pour 1, le premier if vaut True et la fonction rend False aussitôt : rien en dessous ne s'exécute. Pour 12, la boucle trouve le diviseur 2 dès son premier tour et le return False dans la boucle termine la boucle et la fonction ensemble. Pour 7, aucun diviseur n'est trouvé, la boucle se termine normalement et la dernière ligne rend True : cette ligne n'est atteinte que si chaque tour de la boucle a échoué à rendre une valeur.

Méthode — Une fonction par tâche
  1. Nommer la tâche en deux mots, un verbe et son objet, ou une question : count_multiples, is_prime, print_line. S'il faut deux verbes, écrire deux fonctions.
  2. Séparer le calcul de l'affichage : une fonction qui calcule rend sa valeur avec return ; le print appartient au programme qui l'appelle.
  3. Tester chaque fonction seule avec deux ou trois appels avant de s'en servir dans le programme.
Exemple
La fonction mark(k) rend la note d'un élève au contrôle numéro k, pour k de 1 à 4 : l'élève a passé quatre contrôles, et les notes ne sont rangées nulle part ailleurs. La fonction mean(n) doit rendre la moyenne des n premières notes. Quelle est la valeur de chaque appel ?
def mark(k):
    if k == 1:
        return 7
    elif k == 2:
        return 13
    elif k == 3:
        return 13
    else:
        return 15

def mean(n):
    total = 0
    for k in range(1, n + 1):
        total = total + mark(k)
    return total / n
>>> mark(3)
>>> mean(4)
>>> mean(2)

>>> mark(3)
13
>>> mean(4)
12.0
>>> mean(2)
10.0
Les notes ne sont rangées nulle part : chaque fois que le programme en a besoin d'une, il la demande à mark. Le corps de mean est le schéma de l'accumulateur, avec un appel à mark dans la boucle : chaque tour ajoute une note. Pour mean(4), le total vaut 7 + 13 + 13 + 15, soit 48, et 48 / 4 vaut 12.0 : l'opérateur / donne toujours un float, même quand la division tombe juste. Pour mean(2), seules les deux premières notes sont ajoutées, et 20 / 2 vaut 10.0. Le return vient après la boucle, une fois toutes les notes ajoutées.

Exemple
Qu'affiche le programme suivant ?
def print_line(n):
    for i in range(n):
        print("*", end="")
    print()

print_line(3)
print_line(1)
print_line(4)

***
*
****
print_line est une procédure : elle affiche une ligne d'étoiles et ne rend rien. Elle est appelée trois fois, comme trois instructions, chacune sur sa ligne. La boucle qui dessine une ligne, avec son end="" et son print() final, est écrite une seule fois, dans la procédure : chaque ligne est maintenant un appel.

Un return qui vient trop tôt
Quand les deux branches d'un test à l'intérieur d'une boucle se terminent par un return, la fonction se termine au premier tour, quoi qu'auraient dit les autres tours.
def is_prime(n):
    for d in range(2, n):
        if n % d == 0:
            return False
        else:
            return True
>>> is_prime(9)
True
Au premier tour, d vaut 2 ; 9 n'est pas divisible par 2, donc le else rend True sans jamais essayer 3. Le return True doit venir après la boucle : il signifie aucun tour n'a trouvé de diviseur. Les nombres inférieurs à 2, qui n'ont aucun diviseur à essayer, demandent encore leur propre test avant la boucle.
Signaler une erreur
Une procédure utilisée comme valeur
Ranger la valeur d'une procédure range None, en silence.
def say_hello():
    print("Hello")

x = say_hello()
print(x)
Hello
None
L'appel affiche Hello, puis x reçoit None. Quand une valeur est nécessaire, la fonction doit la rendre par return ; quand seule une action est nécessaire, l'appel reste seul sur sa ligne.
Signaler une erreur
Compétences à pratiquer
  • Prédire
  • Composer des appels
  • Lire une moyenne
  • Procédures
III Noms locaux et découpage
Une fonction est un petit programme à l'intérieur du programme. Les noms qu'elle utilise à l'intérieur, ses paramètres et les variables qu'elle crée, lui appartiennent : ils apparaissent quand elle est appelée et disparaissent quand elle rend sa valeur. C'est ce qui permet à deux fonctions, ou à une fonction et au programme principal, d'utiliser le même nom sans se gêner. Une fois cela clair, un programme entier peut se découper en fonctions, une par tâche, et le programme principal devient une courte suite d'appels.
Signaler une erreur
Définition — Les variables locales
Les paramètres d'une fonction et les variables affectées dans son corps sont locaux à la fonction : ils sont créés à chaque appel, n'existent que pendant l'exécution du corps, et disparaissent quand la fonction rend sa valeur. Un nom local est invisible hors de la fonction. Deux fonctions, ou une fonction et le programme principal, peuvent utiliser le même nom pour des variables différentes.
Définition — Recevoir et rendre
Ce dont une fonction a besoin lui arrive par ses paramètres ; ce qu'elle produit repart par return. Une fonction peut lire une variable du programme principal, mais affecter ce nom dans le corps crée une variable locale au lieu de modifier celle de l'extérieur. En règle générale, une fonction ne touche au programme principal que par ses paramètres et par la valeur qu'elle rend.
Exemple
Qu'affiche le programme suivant ?
def add_tax(price):
    total = price * 1.2
    return total

price = 10
total = 0
result = add_tax(price)
print(total, result)

0 12.0
Deux tables de noms : celle de la fonction pendant l'appel, avec ses propres price et total, et celle du programme principal, avec price, total et, une fois l'appel terminé, result.
où nom valeur
programme principal price 10
programme principal total 0
dans add_tax price 10
dans add_tax total 12.0
programme principal, après l'appel result 12.0
Le total de la fonction est local : il vaut 12.0 à l'intérieur, et le total du programme principal reste 0. La valeur ne sort de la fonction que par return, dans result.

Méthode — Découper un programme
  1. Lister les tâches du programme en mots : lire une valeur, calculer quelque chose, afficher un résultat.
  2. Une fonction par tâche : pour chacune, choisir le nom, les paramètres dont elle a besoin et la valeur qu'elle rend.
  3. Écrire et tester chaque fonction seule, avec deux appels, en commençant par celle qui ne dépend de rien.
  4. Écrire le programme principal comme la suite des appels : il lit, appelle, affiche, et ne contient presque aucun calcul lui-même.
Exemple
Le nombre secret tiré est 42 et le joueur tape 50 et 42. Qu'affiche le programme suivant ?
import random

def read_guess():
    return int(input("Your guess? "))

def compare(guess, secret):
    if guess > secret:
        return "too big"
    elif guess < secret:
        return "too small"
    else:
        return "found"

secret = random.randint(1, 100)
answer = ""
attempts = 0
while answer != "found":
    guess = read_guess()
    attempts = attempts + 1
    answer = compare(guess, secret)
    print(answer)
print("Attempts:", attempts)

Your guess? 50
too big
Your guess? 42
found
Attempts: 2
Le jeu du nombre à deviner, découpé en tâches. read_guess n'a pas de paramètre : elle ne dépend de rien, lit un nombre et le rend. compare fait une tâche : étant donné une proposition et le secret, elle rend la réponse sous forme de chaîne, sans rien afficher et sans rien lire. Le programme principal garde la boucle, le compteur et l'affichage. compare se teste seule : compare(50, 42) vaut 'too big'.

Un nom local lu dehors
Une variable créée dans une fonction n'existe plus une fois la fonction terminée.
def add_tax(price):
    total = price * 1.2
    return total

print(add_tax(10))
print(total)
12.0
Traceback (most recent call last):
  File "main.py", line 6, in <module>
    print(total)
          ^^^^^
NameError: name 'total' is not defined
La valeur a été rendue et affichée par le premier print ; le nom total lui-même est resté dans la fonction. Pour garder la valeur, ranger l'appel : total = add_tax(10).
Signaler une erreur
Modifier une variable du programme principal
Affecter dans une fonction un nom qui existe aussi dehors ne modifie pas la variable extérieure ; cela en crée une locale, et la lire avant cette affectation est une erreur.
count = 0

def bump():
    count = count + 1

bump()
Traceback (most recent call last):
  File "main.py", line 6, in <module>
    bump()
  File "main.py", line 4, in bump
    count = count + 1
            ^^^^^
UnboundLocalError: cannot access local variable 'count' where it is not associated with a value
Parce que count est affecté dans le corps, Python le considère comme local, et le côté droit lit une locale qui n'a pas encore de valeur. La manière propre : donner la valeur en paramètre et rendre la nouvelle valeur : bump(count) qui rend count + 1, puis count = bump(count).
Signaler une erreur
L'ordre des arguments
En règle générale, les arguments s'écrivent dans l'ordre des paramètres et leur sont associés par position (un argument nommé comme end="" dans print fait exception). Les échanger ne donne aucune erreur, mais un résultat faux.
def fare(age, price):
    if age < 12:
        return price / 2
    return price
>>> fare(30, 10)
10
>>> fare(10, 30)
15.0
Le second appel donne l'âge 10 et le prix 30 : la moitié de 30 revient, et rien ne signale l'erreur. Relire l'en-tête avant d'appeler, et écrire les arguments dans son ordre.
Signaler une erreur
Compétences à pratiquer
  • Prédire
  • Réécrire en fonctions
IV Projet : une calculatrice à menu
Le projet est une calculatrice : l'utilisateur choisit une opération dans un menu, tape deux nombres, lit le résultat, et recommence jusqu'à la lettre q. Chaque opération est une fonction testée seule ; le menu est une boucle qui les appelle. Le programme se construit à partir de ses fonctions, de bas en haut.
Signaler une erreur
Méthode — Construire un programme à partir de ses fonctions
  1. Écrire d'abord les fonctions, celles qui calculent, et tester chacune avec deux appels.
  2. Écrire la boucle du menu avec une seule opération, l'exécuter, puis ajouter les autres une par une.
  3. Ajouter les cas d'erreur en dernier : une opération inconnue, une division par zéro. Chacun est une branche de plus, pas un nouveau programme.
  4. Exécuter le programme entier sur une courte session qui passe par chaque branche, sortie comprise.
Exemple
L'utilisateur tape +, 3, 4, /, 1, 0 et q. Qu'affiche le programme suivant ?
def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

def multiply(a, b):
    return a * b

def divide(a, b):
    return a / b

op = input("Operation? ")
while op != "q":
    a = float(input("a? "))
    b = float(input("b? "))
    if op == "+":
        print("Result:", add(a, b))
    elif op == "-":
        print("Result:", subtract(a, b))
    elif op == "*":
        print("Result:", multiply(a, b))
    elif op == "/":
        if b == 0:
            print("Cannot divide by zero")
        else:
            print("Result:", divide(a, b))
    else:
        print("Unknown operation")
    op = input("Operation? ")
print("Bye")

Operation? +
a? 3
b? 4
Result: 7.0
Operation? /
a? 1
b? 0
Cannot divide by zero
Operation? q
Bye
Quatre fonctions dont le corps tient en une ligne, testées par des appels comme add(3, 4), et un programme principal qui ne fait que lire, choisir et afficher. Les nombres sont lus avec float, donc les résultats sont des nombres décimaux : 7.0. La division par zéro est attrapée avant d'appeler divide : la fonction elle-même reste simple, la vérification appartient au menu, qui sait quoi afficher. La sentinelle de la boucle est la lettre q, lue une fois avant la boucle et de nouveau à la fin de chaque tour. Les exercices construisent ce programme selon les trois étapes de la méthode, fonctions d'abord.

Compétences à pratiquer
  • Construire la calculatrice et ses variantes
V Pour aller plus loin
Compétences à pratiquer
  • Aller plus loin avec les fonctions
  • Quiz