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.
printrenvoie une valeur,returnl’affiche - B.
printaffiche une valeur à l’écran,returnrenvoie une valeur à l’appelant - C.
printetreturnfont exactement la même chose - D.
returnn’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 sifutiliseprintau lieu dereturn,f(x)vautNone. - D est faux :
returnne 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 nonNone). - 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, passtr. - 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
returnn’est pas une erreur de syntaxe. - B est faux : la valeur par défaut est
None, pas0. - 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 :
returnrenvoie une valeur réutilisable : on peut l’affecter à une variable ou la tester dans unif.printse contente d’afficher : si on remplaçaitreturnparprint,est_divisible(12, 3)vaudraitNone.- Les annotations de type (
int,bool) et la docstring documentent le contrat de la fonction.
Exercice 1 – Paquets et poids
É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 renvoyerTrue, sinon elle doit renvoyerFalse.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
- É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
Trueil affiche :"envoi Ok"; - sinon il affiche :
"envoi impossible".
- si elle renvoie
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 afficher6(qui est le résultat de \(1\times2\times3\)) ;print(produit(5))doit écrire120(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 entrant4567et8devra renvoyer6;moyenne(3)en entrant45et6devra renvoyer5.
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) :
i | a | b |
|---|---|---|
| — | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 3 |
| 4 | 3 | 5 |
| 5 | 5 | 8 |
| 6 | 8 | 13 |
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 renvoyerTrue;est_armstrong(154)doit renvoyerFalse(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 :
- On convertit
nen chaîne avecstr(n)pour pouvoir parcourir ses chiffres. - Pour chaque caractère, on le reconvertit en entier avec
int(caractere). - On accumule la somme des cubes.
- 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 renvoyerFalse;presence_de_A("CTTACT")renvoieTrue.
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")renvoie3;position_de_AT("GATATAT")renvoie1;position_de_AT("GACCGTA")renvoieNone
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 renvoyer2;position("GT", "TACGTTA")doit renvoyer3;position("GTTA", "TACGTTA")doit renvoyer3;position("TAC", "TACGTTA")doit renvoyer0;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 »).