06 - Fonctions (2)

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
    
  1. É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".
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 qui recueille au clavier les températures de 10 jours successifs, calcule et renvoie les températures minimale, maximale et moyenne.

def stat_temp() -> tuple :
    """Fonction qui :
          1. Lit (avec input) les températures sur 10 jours
          2. Calcule le min, le max et la moyenne
          3. Renvoie les 3 valeurs

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

    Sortie
    ------
        tmin (float), tmoy (float), tmax (float)
    """
    # compléter ici

    return tmin, tmax, tmoy
Correction
def stat_temp() -> tuple:
    """Lit 10 températures et renvoie (min, max, 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
    return tmin, tmax, tmoy

# Appel
minimum, maximum, moyenne = stat_temp()
print(f"Min : {minimum}, Max : {maximum}, Moyenne : {moyenne:.1f}")

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.

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
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
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
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
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
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(2_000) 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
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
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) -> int:
    """
    Documentation
    """
    pass
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
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 la sous-chaîne sequence[i:i+m]. Dès qu’une lettre diffère, on passe à la position suivante.

Complexité : dans le pire cas, \(O(n \times m)\) où \(n\) est la longueur de la séquence et \(m\) celle du code.

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