07 - Fonctions (2)

Les exercices se font dans Thonny, dans le fichier 07_fonctions_2.py.

Cours associé : 08 - Fonctions .

Exercice 0 : QCM – vérification des prérequis

Pour chaque question, une seule réponse est correcte.

1. Quelle est la différence entre print et return dans une fonction ?

  • A. print renvoie une valeur, return l’affiche
  • B. print affiche une valeur à l’écran, return renvoie une valeur à l’appelant
  • C. print et return font exactement la même chose
  • D. return n’est utilisable que dans le programme principal
Correction

Réponse : B. print affiche un texte dans la console (effet de bord), tandis que return renvoie une valeur que l’appelant peut stocker dans une variable ou utiliser dans une expression.

  • A est faux : c’est exactement l’inverse.
  • C est faux : print(f(x)) affiche le résultat, mais si f utilise print au lieu de return, f(x) vaut None.
  • D est faux : return ne peut être utilisé que dans une fonction, pas dans le programme principal.

2. Que vaut x après l’exécution du code suivant ?

def double(n):
    return n * 2

x = double(5) + 1
  • A. 10
  • B. 11
  • C. None
  • D. Une erreur
Correction

Réponse : B. double(5) renvoie 10, puis 10 + 1 donne 11.

  • A est faux : on a oublié le + 1.
  • C est faux : la fonction utilise bien return, elle renvoie donc une valeur (et non None).
  • D est faux : le code est parfaitement valide.

3. Quelle annotation de type correspond à une fonction qui prend un entier et renvoie un booléen ?

  • A. def f(n: bool) -> int:
  • B. def f(n: int) -> bool:
  • C. def f(n: int) -> str:
  • D. def f(n) -> int:
Correction

Réponse : B. n: int indique que le paramètre est un entier, et -> bool indique que la valeur de retour est un booléen. Les annotations de type documentent le code mais ne sont pas vérifiées à l’exécution par Python.

  • A est faux : les types sont inversés.
  • C est faux : le type de retour devrait être bool, pas str.
  • D est faux : il manque l’annotation du paramètre, et le type de retour devrait être bool.

4. Que se passe-t-il si une fonction ne contient pas de return ?

  • A. Python lève une erreur SyntaxError
  • B. La fonction renvoie 0
  • C. La fonction renvoie None
  • D. La fonction ne peut pas être appelée
Correction

Réponse : C. En Python, une fonction sans return (ou avec return sans valeur) renvoie automatiquement None. C’est le comportement par défaut.

  • A est faux : l’absence de return n’est pas une erreur de syntaxe.
  • B est faux : la valeur par défaut est None, pas 0.
  • D est faux : on peut tout à fait appeler une telle fonction, elle renvoie simplement None.

Exemple travaillé : construire une fonction avec return

Problème : écrire une fonction qui détermine si un nombre est divisible par un autre, puis l’utiliser dans un programme.

Étape 1. Prototype (signature, annotations de type et documentation) :

def est_divisible(a: int, b: int) -> bool:
    """Détermine si a est divisible par b.

    Paramètres :
        a (int) : le nombre à tester
        b (int) : le diviseur (> 0)

    Retour :
        bool : True si a est divisible par b, False sinon
    """

Étape 2. Corps de la fonction :

    return a % b == 0

La comparaison a % b == 0 renvoie directement un booléen. Il est inutile d’écrire if a % b == 0: return True else: return False.

Étape 3. Tests avec assert :

assert est_divisible(12, 3) == True   # 12 = 3 × 4
assert est_divisible(12, 5) == False  # 12 = 5 × 2 + 2
assert est_divisible(0, 7) == True    # 0 est divisible par tout entier
print("Tous les tests passent.")

Étape 4. Utilisation dans un programme :

n = int(input("Entrer un nombre : "))
if est_divisible(n, 2):
    print(f"{n} est pair")
else:
    print(f"{n} est impair")

Points clés :

  • return renvoie une valeur réutilisable : on peut l’affecter à une variable ou la tester dans un if.
  • print se contente d’afficher : si on remplaçait return par print, est_divisible(12, 3) vaudrait None.
  • Les annotations de type (int, bool) et la docstring documentent le contrat de la fonction.

Exercice 1 – Paquets et poids

  1. Écrire une fonction poids_total(n, poids) qui prend en paramètres deux entiers : un nombre de paquets et le poids d’un paquet (tous les paquets ont le même poids). Si le poids total est strictement inférieur à 105 kg, la fonction doit renvoyer True, sinon elle doit renvoyer False.

    def poids_total(n : int, poids : int) -> bool:
        """Fonction qui détermine si le poids total est strictement inférieur à 105 kg.
    
        Paramètres
        ----------
            n (int)
            poids (int)
    
        Sortie
        ------
            True (bool) ou False (bool)
        """
        pass
    
    Indice

    Calculer le poids total n * poids : sa comparaison avec 105 vaut déjà True ou False, comme dans l’exemple travaillé.

  2. Écrire le programme principal qui doit reproduire cette situation : on veut faire un premier envoi de 10 paquets de 13 kg chacun, puis un second envoi de 12 paquets de 5 kg chacun. Pour chaque envoi, le programme doit appeler la fonction et :

    • si elle renvoie True il affiche : "envoi Ok" ;
    • sinon il affiche : "envoi impossible".
    Indice

    Appeler poids_total dans la condition d’un if, une fois pour chaque envoi.

Correction

1.

def poids_total(n: int, poids: int) -> bool:
    """Détermine si le poids total est strictement inférieur à 105 kg."""
    return n * poids < 105

La comparaison n * poids < 105 renvoie directement un booléen. Il est inutile d’écrire if n * poids < 105: return True else: return False.

2.

# Premier envoi : 10 paquets de 13 kg = 130 kg > 105
if poids_total(10, 13):
    print("envoi Ok")
else:
    print("envoi impossible")

# Second envoi : 12 paquets de 5 kg = 60 kg < 105
if poids_total(12, 5):
    print("envoi Ok")
else:
    print("envoi impossible")

Résultat : le premier envoi affiche « envoi impossible » (130 ≥ 105), le second affiche « envoi Ok » (60 < 105).

Exercice 2 – Température

Écrire une fonction stat_temp() qui recueille au clavier les températures de 10 jours successifs, puis affiche les températures minimale, maximale et moyenne.

def stat_temp():
    """Fonction qui :
          1. lit (avec input) les températures sur 10 jours ;
          2. calcule le minimum, le maximum et la moyenne ;
          3. affiche ces trois valeurs.

    Paramètres
    ----------
        Aucun

    Sortie
    ------
        Aucune : la fonction affiche ses résultats.
    """
    # compléter ici
Indice

Lire la première température avant la boucle : elle sert de valeur de départ au minimum, au maximum et à la somme. Lire ensuite les neuf autres dans une boucle, en mettant à jour les trois variables à chaque lecture.

Code à copier puis compléter
def stat_temp():
    t = float(input("Température du jour 1 : "))
    tmin = t
    tmax = t
    somme = t
    for jour in range(2, 11):
        t = float(input(f"Température du jour {jour} : "))
        if t < tmin:
            tmin = t
        if ...:
            ...
        somme = ...
    tmoy = ...
    print(f"Min : {tmin}, Max : {tmax}, Moyenne : {round(tmoy, 1)}")
Correction
def stat_temp():
    """Lit 10 températures, puis affiche le minimum, le maximum et la moyenne."""
    t = float(input("Température du jour 1 : "))
    tmin = t
    tmax = t
    somme = t

    for jour in range(2, 11):
        t = float(input(f"Température du jour {jour} : "))
        if t < tmin:
            tmin = t
        if t > tmax:
            tmax = t
        somme += t

    tmoy = somme / 10
    print(f"Min : {tmin}, Max : {tmax}, Moyenne : {round(tmoy, 1)}")

# Appel
stat_temp()

On initialise tmin et tmax avec la première température lue, puis on les met à jour à chaque nouvelle lecture. La moyenne est calculée en divisant la somme par 10. Renvoyer les trois valeurs à la fois sera possible avec une structure étudiée plus loin ; ici, la fonction affiche ses résultats.

Exercice 3 – Produit des entiers successifs

Écrire une fonction produit(n) qui prend un nombre n en paramètre et qui renvoie le produit des entiers successifs de 1 jusqu’à n.

Exemples :

  • print(produit(3)) doit afficher 6 (qui est le résultat de \(1\times2\times3\)) ;
  • print(produit(5)) doit écrire 120 (qui est le résultat de \(1\times2\times3\times4\times5\)).
def produit(n: int) -> int:
    """
    Documentation
    """
    pass
Indice

Partir de resultat = 1, puis multiplier successivement par chaque entier de 1 à n. Pourquoi ne pas partir de 0 ?

Correction
def produit(n: int) -> int:
    """Renvoie le produit des entiers de 1 à n (factorielle de n).

    >>> produit(3)
    6
    >>> produit(5)
    120
    >>> produit(1)
    1
    """
    resultat = 1
    for i in range(1, n + 1):
        resultat = resultat * i
    return resultat

On initialise resultat à 1 (élément neutre de la multiplication) et on multiplie successivement par chaque entier de 1 à \(n\). C’est la factorielle de \(n\), notée \(n!\).

Piège : initialiser resultat à 0 donne toujours 0 (car \(0 \times k = 0\)).

Exercice 4 – Moyenne

Écrire une fonction qui calcule la moyenne d’un certain nombre de notes entrées par l’utilisateur. Cette fonction prendra comme paramètre un nombre n, qui correspond au nombre de notes qui seront demandées à l’utilisateur. En sortie, elle renverra la moyenne des différentes notes.

Pour tester :

  • moyenne(5) en entrant 4 5 6 7 et 8 devra renvoyer 6 ;
  • moyenne(3) en entrant 4 5 et 6 devra renvoyer 5.
def moyenne(n: int ) -> float:
    """
    Documentation
    """
    pass
Indice

Accumuler les notes saisies dans une variable somme, dans une boucle qui tourne n fois, puis renvoyer la somme divisée par n.

Correction
def moyenne(n: int) -> float:
    """Lit n notes au clavier et renvoie leur moyenne.

    Paramètre : n (int), nombre de notes à saisir
    Sortie : (float), la moyenne des n notes
    """
    somme = 0
    for i in range(n):
        note = float(input(f"Note {i + 1} : "))
        somme += note
    return somme / n

Schéma classique de l’accumulateur : on additionne les notes une par une, puis on divise par le nombre total.

Tests : moyenne(5) avec 4, 5, 6, 7, 8 → somme = 30, moyenne = 6,0. moyenne(3) avec 4, 5, 6 → somme = 15, moyenne = 5,0.

Exercice 5 – Années bissextiles

Les années bissextiles sont les années divisibles par 4, à l’exception de celles qui sont divisibles par 100 sans l’être par 400.

Par exemple :

  • 2000 est une année bissextile (le nombre 2000 est divisible par 4, par 100 mais aussi par 400 : il ne fait donc pas partie des exceptions).
  • 2012 est divisible par 4 mais pas par 100 : 2012 est bissextile.
  • 1900 est divisible par 4, par 100 mais pas par 400 : 1900 n’est pas bissextile.

Définissez une fonction dont le paramètre est un entier naturel non nul (l’année) puis qui renvoie True lorsque l’année est bissextile et False dans le cas contraire.

def bissextile(annee):
    """Fonction qui détermine si une année donnée est bissextile.

    Paramètres
    ----------
        annee (int) : entier strictement positif

    Sortie
    ------
        True (bool) si annee est bissextile, False (bool) sinon
    """
    pass
Indice

Tester les cas du plus particulier au plus général : divisible par 400, sinon divisible par 100, sinon divisible par 4.

Code à copier puis compléter
def bissextile(annee):
    if annee % 400 == 0:
        return True
    elif ...:
        return False
    elif ...:
        ...
    else:
        ...
Correction
def bissextile(annee: int) -> bool:
    """Détermine si une année est bissextile.

    >>> bissextile(2000)
    True
    >>> bissextile(2012)
    True
    >>> bissextile(1900)
    False
    >>> bissextile(2023)
    False
    """
    if annee % 400 == 0:
        return True
    elif annee % 100 == 0:
        return False
    elif annee % 4 == 0:
        return True
    else:
        return False

Version concise :

def bissextile(annee: int) -> bool:
    return (annee % 4 == 0 and annee % 100 != 0) or (annee % 400 == 0)

Logique : une année est bissextile si elle est divisible par 4, sauf si elle est divisible par 100 (auquel cas elle ne l’est pas), sauf si elle est aussi divisible par 400 (auquel cas elle l’est quand même).

Tests : 2000 → divisible par 400 → bissextile. 1900 → divisible par 100 mais pas par 400 → pas bissextile. 2012 → divisible par 4 mais pas par 100 → bissextile. 2023 → pas divisible par 4 → pas bissextile.

Exercice 6 – Suite de Fibonacci

On appelle nombres de Fibonacci les nombres obtenus par somme des deux précédents.

Les deux premiers nombres de Fibonacci sont \(F_0=1\) et \(F_1=1\).

On a ensuite \(F_2=2\), \(F_3=3\), \(F_4=5\), \(F_5=8\), \(F_6=13\), etc.

Complétez le corps de la fonction fibo() suivante en respectant les spécifications décrites dans son prototype (docstring).

def fibo(n):
    """Fonction qui calcule le n-ième terme de la suite de Fibonacci.

    Paramètres
    ----------
        n (int) : nombre entier naturel.

    Sortie
    ------
        (int) : n-ième terme de la suite de Fibonacci.
    """
    pass
Indice

Garder les deux derniers termes dans deux variables a et b, qui valent 1 au départ. À chaque tour, le nouveau terme est a + b : décaler ensuite les deux variables, comme à l’exercice 15 de la fiche 04.

Code à copier puis compléter
def fibo(n):
    if n == 0 or n == 1:
        return 1
    a, b = 1, 1
    for i in range(2, n + 1):
        a, b = ..., ...
    return b
Correction
def fibo(n: int) -> int:
    """Renvoie le n-ième terme de la suite de Fibonacci.

    >>> fibo(0)
    1
    >>> fibo(1)
    1
    >>> fibo(6)
    13
    >>> fibo(10)
    89
    """
    if n == 0 or n == 1:
        return 1

    a, b = 1, 1
    for i in range(2, n + 1):
        a, b = b, a + b
    return b

Principe : on maintient deux variables a et b contenant les deux derniers termes. À chaque itération, on les met à jour avec l’affectation simultanée a, b = b, a + b.

Trace pour fibo(6) :

iab
—11
212
323
435
558
6813

Résultat : b = 13.

Exercice 7 – Nombres de Armstrong

Un nombre de Armstrong est un entier positif égal à la somme des cubes de ses chiffres.

Exemple : \(153 = 1^3 + 5^3 + 3^3 = 1 +125 +27 = 153\).

Écrire une fonction est_armstrong qui prend un nombre n en entrée. Cette fonction doit renvoyer True si ce nombre est un nombre de Armstrong et False sinon.

Pour tester :

  • est_armstrong(153) doit renvoyer True ;
  • est_armstrong(154) doit renvoyer False (car \(1^3 + 5^3 + 4^3 =1 + 125 + 64=190\) qui est différent de \(154\)).

Aide : il faut convertir le nombre n en chaîne de caractères pour pouvoir parcourir tous les chiffres du nombre un par un, puis reconvertir en entier pour calculer le cube de chacun d’entre eux.

def est_armstrong(n : int) -> bool:
    """Fonction qui détermine si un entier est un nombre de Armstrong ou non.

    Paramètres
    ----------
        n (int) : entier naturel

    Sortie
    ------
        True (bool) si n est un nombre de Armstrong, False (bool) sinon

    """
    pass
Indice

Parcourir str(n) caractère par caractère, accumuler les cubes des chiffres dans une variable, puis comparer cette somme à n.

Correction
def est_armstrong(n: int) -> bool:
    """Détermine si n est un nombre de Armstrong.

    >>> est_armstrong(153)
    True
    >>> est_armstrong(154)
    False
    >>> est_armstrong(370)
    True
    >>> est_armstrong(1)
    True
    """
    chaine = str(n)
    somme = 0
    for caractere in chaine:
        chiffre = int(caractere)
        somme += chiffre ** 3
    return somme == n

Étapes :

  1. On convertit n en chaîne avec str(n) pour pouvoir parcourir ses chiffres.
  2. Pour chaque caractère, on le reconvertit en entier avec int(caractere).
  3. On accumule la somme des cubes.
  4. On compare la somme à n.

Piège courant : oublier la conversion int(caractere) et calculer caractere ** 3 sur une chaîne (ce qui lève une erreur TypeError).

Exercice 8 – Nombres de Armstrong (suite)

Écrire une fonction armstrong qui affiche les nombres de Armstrong inférieurs à un nombre n passé en paramètre.

Exemple : armstrong(2000) doit afficher les valeurs suivantes :

1
153
370
371
407

Aide : il est fortement conseillé de réutiliser la fonction est_armstrong(n) qui vérifie si un nombre n est un nombre de Armstrong.

def armstrong(n: int ) -> None:
    """
    Documentation
    """
    pass
Indice

Parcourir les entiers de 1 à n - 1 et afficher ceux pour lesquels est_armstrong renvoie True.

Correction
def armstrong(n: int) -> None:
    """Affiche tous les nombres de Armstrong strictement inférieurs à n.

    >>> armstrong(500)
    1
    153
    370
    371
    407
    """
    for i in range(1, n):
        if est_armstrong(i):
            print(i)

On teste chaque nombre de 1 à \(n - 1\) en réutilisant la fonction est_armstrong. C’est un exemple de réutilisation de fonction : on décompose le problème en sous-problèmes.

Remarque : le type de retour est None car la fonction se contente d’afficher, elle ne renvoie rien.

Exercice 9 – ADN

Une molécule d’ADN est formée d’environ six milliards de nucléotides. L’ordinateur est donc un outil indispensable pour l’analyse de l’ADN. Dans un brin d’ADN il y a seulement quatre types de nucléotides qui sont notés A, C, T ou G. Une séquence d’ADN est donc un long mot de la forme : TAATTACAGACCTGAA...

Écrire une fonction presence_de_A(sequence) qui teste la présence du nucléotide A dans une séquence d’ADN.

Tests :

  • presence_de_A("CTTGCT") doit renvoyer False ;
  • presence_de_A("CTTACT") renvoie True.
def presence_de_A(sequence: str) -> bool:
    """
    Documentation
    """
    pass
Indice

Parcourir la séquence : dès qu’un A est trouvé, renvoyer True. Après la boucle seulement, renvoyer False : pourquoi pas dans un else à l’intérieur de la boucle ?

Correction
def presence_de_A(sequence: str) -> bool:
    """Teste la présence du nucléotide A dans une séquence d'ADN.

    >>> presence_de_A("CTTGCT")
    False
    >>> presence_de_A("CTTACT")
    True
    >>> presence_de_A("")
    False
    >>> presence_de_A("AAAA")
    True
    """
    for nucleotide in sequence:
        if nucleotide == "A":
            return True
    return False

C’est un schéma classique de recherche séquentielle : dès qu’on trouve un A, on renvoie True. Si on termine la boucle sans en avoir trouvé, on renvoie False.

Piège courant : écrire else: return False à l’intérieur de la boucle, ce qui renvoie False dès le premier caractère différent de A.

Exercice 10 – ADN 2 (difficile)

Écrire une fonction position_de_AT(sequence) qui teste si une séquence contient le nucléotide A suivi du nucléotide T et renvoie l’indice de la première occurrence trouvée.

Exemples :

  • position_de_AT("CTTATGCT") renvoie 3 ;
  • position_de_AT("GATATAT") renvoie 1 ;
  • position_de_AT("GACCGTA") renvoie None
def position_de_AT(sequence: str):
    """
    Documentation
    """
    pass
Indice

Parcourir les indices i et comparer sequence[i] à "A" et sequence[i + 1] à "T". Jusqu’où i peut-il aller sans que sequence[i + 1] sorte de la chaîne ?

Code à copier puis compléter
def position_de_AT(sequence: str):
    for i in range(len(sequence) - 1):
        if ... and ...:
            return i
    return None
Correction
def position_de_AT(sequence: str):
    """Renvoie l'indice de la première occurrence de 'AT' dans la séquence.
    Renvoie None si 'AT' n'est pas trouvé.

    >>> position_de_AT("CTTATGCT")
    3
    >>> position_de_AT("GATATAT")
    1
    >>> position_de_AT("GACCGTA")
    """
    for i in range(len(sequence) - 1):
        if sequence[i] == "A" and sequence[i + 1] == "T":
            return i
    return None

On parcourt la séquence jusqu’à l’avant-dernier caractère (car on compare sequence[i] et sequence[i + 1]). Dès qu’on trouve A suivi de T, on renvoie l’indice i.

Attention : range(len(sequence) - 1) et non range(len(sequence)), sinon sequence[i + 1] provoquerait un IndexError au dernier indice.

Exercice 11 – ADN 3 (encore plus difficile)

Écrire une fonction position(code, sequence) qui teste si une séquence contient un code donné et renvoie l’indice de la première occurrence. Si la séquence n’est pas trouvée, la fonction doit renvoyer -1.

Exemples :

  • position("CCG", "CTCCGTT") doit renvoyer 2 ;
  • position("GT", "TACGTTA") doit renvoyer 3 ;
  • position("GTTA", "TACGTTA") doit renvoyer 3 ;
  • position("TAC", "TACGTTA") doit renvoyer 0 ;
  • position("GAT", "TACGTTA") doit renvoyer -1
def position(code: str, sequence: str) -> int:
    """
    Documentation
    """
    pass
Indice

Pour chaque indice i où le code peut commencer, comparer le code caractère par caractère avec la séquence à partir de i ; une variable booléenne retient si tous les caractères correspondent.

Code à copier puis compléter
def position(code: str, sequence: str) -> int:
    n = len(sequence)
    m = len(code)
    for i in range(n - m + 1):
        trouve = True
        for j in range(m):
            if sequence[i + j] != ...:
                trouve = False
        if trouve:
            return ...
    return -1
Correction
def position(code: str, sequence: str) -> int:
    """Renvoie l'indice de la première occurrence de code dans sequence.
    Renvoie -1 si code n'est pas trouvé.

    >>> position("CCG", "CTCCGTT")
    2
    >>> position("GT", "TACGTTA")
    3
    >>> position("GTTA", "TACGTTA")
    3
    >>> position("TAC", "TACGTTA")
    0
    >>> position("GAT", "TACGTTA")
    -1
    """
    n = len(sequence)
    m = len(code)

    for i in range(n - m + 1):
        # Vérifier si code apparaît à la position i
        trouve = True
        for j in range(m):
            if sequence[i + j] != code[j]:
                trouve = False
                break
        if trouve:
            return i

    return -1

Principe : pour chaque position i dans la séquence, on compare, caractère par caractère, le code avec les m caractères de la séquence qui commencent à l’indice i. Dès qu’une lettre diffère, on passe à la position suivante.

Remarque : on retrouve ici le même algorithme que la recherche de mot dans un texte (exercice de la fiche « Parcours de liste »).