Aller au contenu

Chapitre 05 — Les fonctions

Durée estimée : 5 à 6 heures (cours, exercices et mini-projet)

Prérequis : Chapitre 04 — Les structures de données (listes, tuples, dictionnaires, ensembles)

Objectifs — à la fin de ce chapitre, tu sauras…

  • définir une fonction avec def et l'appeler ;
  • transmettre des informations avec des paramètres et récupérer un résultat avec return ;
  • utiliser des valeurs par défaut, des arguments nommés, *args et **kwargs ;
  • comprendre la portée des variables (locale ou globale) ;
  • documenter une fonction avec une docstring ;
  • écrire une fonction récursive simple et découper un programme en fonctions.

Cours

1. Pourquoi des fonctions ?

Tu utilises déjà des fonctions depuis le début : print(), len(), input(), sum()… Ce sont des morceaux de code prêts à l'emploi, auxquels on donne des informations et qui font un travail.

Imagine maintenant que, dans ton programme, tu doives afficher un joli cadre autour d'un titre à cinq endroits différents. Tu pourrais copier-coller cinq fois les mêmes lignes… mais le jour où tu veux changer le style du cadre, il faudra modifier les cinq copies (et tu en oublieras une).

Une fonction, c'est une recette de cuisine : tu l'écris une fois, tu lui donnes un nom (« pâte à crêpes »), et ensuite il suffit de dire « fais une pâte à crêpes » pour qu'elle soit exécutée. Les ingrédients sont les paramètres, le plat obtenu est la valeur de retour.

Les fonctions permettent de :

  • ne pas se répéter (principe DRY : Don't Repeat Yourself) ;
  • découper un gros problème en petits problèmes simples ;
  • nommer une action, ce qui rend le code lisible comme un texte ;
  • tester chaque morceau séparément (on le fera au chapitre 13).

2. Définir et appeler une fonction

On définit une fonction avec le mot-clé def, suivi de son nom, de parenthèses et de deux-points. Le corps de la fonction est indenté, comme pour un if ou un for.

def dire_bonjour():
    print("Bonjour !")
    print("Bienvenue dans le cours Python.")

Si tu exécutes uniquement ce code, rien ne s'affiche ! On a seulement écrit la recette, on ne l'a pas encore cuisinée. Pour exécuter la fonction, il faut l'appeler en écrivant son nom suivi de parenthèses :

def dire_bonjour():
    print("Bonjour !")
    print("Bienvenue dans le cours Python.")


dire_bonjour()
dire_bonjour()
Bonjour !
Bienvenue dans le cours Python.
Bonjour !
Bienvenue dans le cours Python.

Quelques règles :

  • Le nom suit les mêmes règles que les variables : minuscules, mots séparés par _. Choisis de préférence un verbe qui décrit l'action : calculer_moyenne, afficher_menu, demander_age.
  • La fonction doit être définie avant d'être appelée (plus haut dans le fichier).
  • Par convention (PEP 8, voir le chapitre 13), on laisse deux lignes vides avant et après une définition de fonction.

Si tu appelles une fonction avant de l'avoir définie :

saluer()

def saluer():
    print("Salut")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'saluer' is not defined

Python lit ton fichier de haut en bas : au moment de l'appel, il ne connaît pas encore saluer.


3. Les paramètres : donner des informations à la fonction

Une fonction devient vraiment utile quand on peut lui transmettre des données. On déclare des paramètres entre les parenthèses : ce sont des variables qui recevront les valeurs au moment de l'appel.

def saluer(prenom):
    print(f"Bonjour {prenom} !")


saluer("Alice")
saluer("Bob")
Bonjour Alice !
Bonjour Bob !

Vocabulaire : - paramètre : le nom dans la définition (prenom) ; - argument : la valeur effectivement transmise lors de l'appel ("Alice").

On peut avoir plusieurs paramètres, séparés par des virgules :

def afficher_produit(nom, prix, quantite):
    total = prix * quantite
    print(f"{quantite} x {nom} à {prix:.2f} € = {total:.2f} €")


afficher_produit("Croissant", 1.10, 4)
afficher_produit("Baguette", 1.25, 2)
4 x Croissant à 1.10 € = 4.40 €
2 x Baguette à 1.25 € = 2.50 €

Les arguments sont associés aux paramètres dans l'ordre : le premier argument va dans le premier paramètre, etc. C'est pourquoi on les appelle des arguments positionnels. Si tu en donnes trop ou pas assez, Python proteste :

def saluer(prenom):
    print(f"Bonjour {prenom} !")


saluer()
Traceback (most recent call last):
  File "<stdin>", line 5, in <module>
TypeError: saluer() missing 1 required positional argument: 'prenom'

4. return : renvoyer un résultat

Jusqu'ici, nos fonctions affichent quelque chose. Mais souvent, on veut qu'une fonction calcule une valeur et nous la donne pour qu'on puisse s'en servir ensuite. C'est le rôle de return.

def carre(nombre):
    return nombre * nombre


resultat = carre(5)
print(resultat)              # → 25
print(carre(3) + carre(4))   # → 25

L'appel carre(5) est remplacé par la valeur renvoyée, exactement comme len([1, 2, 3]) est remplacé par 3. On peut donc l'utiliser dans un calcul, le stocker, le passer à une autre fonction…

C'est LA confusion du débutant. Compare :

def double_affiche(x):
    print(x * 2)


def double_renvoie(x):
    return x * 2


a = double_affiche(10)    # affiche 20, mais...
b = double_renvoie(10)    # n'affiche rien, mais...
print(a)                  # ... a vaut None !
print(b)                  # ... b vaut bien 20
20
None
20
  • print montre une valeur à l'humain qui regarde l'écran. Le programme, lui, ne peut rien en faire.
  • return donne une valeur au programme, qui peut la réutiliser.

Analogie : demander à un collègue de calculer un total. Avec print, il crie le résultat dans le couloir : tu l'entends, mais tu ne l'as pas sur papier. Avec return, il te tend une feuille avec le résultat : tu peux la garder et t'en servir.

Règle d'or : une fonction qui calcule doit renvoyer son résultat ; c'est le code qui l'appelle qui décide de l'afficher ou non.

Une fonction sans return renvoie None

Si une fonction se termine sans rencontrer de return, elle renvoie automatiquement None, la valeur qui signifie « rien ».

return arrête la fonction immédiatement

Dès qu'un return est exécuté, la fonction s'arrête ; les lignes suivantes sont ignorées. On s'en sert souvent pour sortir tôt :

def est_majeur(age):
    if age >= 18:
        return True
    return False


print(est_majeur(20))   # → True
print(est_majeur(15))   # → False

On peut d'ailleurs écrire plus directement return age >= 18, puisque la comparaison est déjà un booléen.

def mention(note):
    if note >= 16:
        return "Très bien"
    if note >= 14:
        return "Bien"
    if note >= 12:
        return "Assez bien"
    if note >= 10:
        return "Passable"
    return "Insuffisant"


for note in [17, 14.5, 10, 8]:
    print(note, "→", mention(note))
17 → Très bien
14.5 → Bien
10 → Passable
8 → Insuffisant

Pas besoin de elif ici : si la première condition est vraie, le return a déjà quitté la fonction.

Renvoyer plusieurs valeurs

Une fonction peut renvoyer plusieurs valeurs séparées par des virgules : elle renvoie en fait un tuple, qu'on déballe à l'arrivée (cf. chapitre 04).

def min_max_moyenne(nombres):
    moyenne = sum(nombres) / len(nombres)
    return min(nombres), max(nombres), moyenne


petit, grand, moy = min_max_moyenne([12, 8, 15, 19])
print(petit, grand, moy)   # → 8 19 13.5

5. Valeurs par défaut et arguments nommés

Les valeurs par défaut

On peut donner une valeur par défaut à un paramètre : s'il n'est pas fourni à l'appel, c'est elle qui sera utilisée.

def saluer(prenom, salutation="Bonjour"):
    print(f"{salutation} {prenom} !")


saluer("Alice")              # → Bonjour Alice !
saluer("Bob", "Salut")       # → Salut Bob !

Tu connais déjà ce mécanisme : print() a un paramètre end qui vaut "\n" par défaut, et sorted() a reverse=False.

Les paramètres avec valeur par défaut doivent être placés après ceux qui n'en ont pas. def f(a=1, b): provoque une SyntaxError.

Les arguments nommés

À l'appel, on peut préciser le nom du paramètre : nom=valeur. L'ordre n'a alors plus d'importance, et surtout, le code devient beaucoup plus clair.

def creer_compte(pseudo, age, pays="France", newsletter=False):
    print(f"{pseudo}, {age} ans, {pays}, newsletter : {newsletter}")


creer_compte("alice42", 25)
creer_compte("bob_du_75", 31, newsletter=True)
creer_compte(age=19, pseudo="chloe", pays="Belgique")
alice42, 25 ans, France, newsletter : False
bob_du_75, 31 ans, France, newsletter : True
chloe, 19 ans, Belgique, newsletter : False

Compare creer_compte("bob_du_75", 31, "France", True) et creer_compte("bob_du_75", 31, newsletter=True) : dans le second cas, on comprend immédiatement ce que signifie True.

Les arguments positionnels doivent toujours venir avant les arguments nommés à l'appel.


6. *args et **kwargs : un nombre variable d'arguments

*args : autant d'arguments positionnels qu'on veut

Comment print() peut-elle accepter 1, 2 ou 10 arguments ? Grâce à l'étoile *. Un paramètre précédé de * récupère tous les arguments positionnels restants dans un tuple.

def moyenne(*nombres):
    print(f"Reçu : {nombres}")
    if len(nombres) == 0:
        return 0
    return sum(nombres) / len(nombres)


print(moyenne(12, 15))
print(moyenne(8, 10, 12, 14))
print(moyenne())
Reçu : (12, 15)
13.5
Reçu : (8, 10, 12, 14)
11.0
Reçu : ()
0

Le nom args est une simple convention (arguments) ; c'est l'étoile qui compte. Ici, nombres est plus parlant.

**kwargs : autant d'arguments nommés qu'on veut

Deux étoiles ** récupèrent tous les arguments nommés non prévus dans un dictionnaire (kwargs = keyword arguments).

def fiche(nom, **infos):
    print(f"--- {nom} ---")
    for cle, valeur in infos.items():
        print(f"{cle} : {valeur}")


fiche("Alice", age=30, ville="Lyon", metier="développeuse")
fiche("Bob")
--- Alice ---
age : 30
ville : Lyon
metier : développeuse
--- Bob ---

L'étoile à l'appel : déballer une collection

Inversement, à l'appel, * déballe une liste en arguments positionnels, et ** déballe un dictionnaire en arguments nommés :

def addition(a, b, c):
    return a + b + c


valeurs = [1, 2, 3]
print(addition(*valeurs))      # → 6  (équivaut à addition(1, 2, 3))

options = {"b": 20, "c": 30}
print(addition(10, **options)) # → 60  (équivaut à addition(10, b=20, c=30))

L'ordre complet des paramètres dans une définition est : def f(positionnels, avec_defaut=..., *args, **kwargs). Tu n'auras pas souvent besoin de tout combiner : *args et **kwargs sont surtout utiles pour écrire des fonctions très souples, et tu les croiseras beaucoup en lisant le code des bibliothèques.


7. La portée des variables : locale ou globale

Une variable créée à l'intérieur d'une fonction est locale : elle n'existe que pendant l'exécution de la fonction, et n'est pas visible à l'extérieur.

def calculer():
    resultat = 42
    print(f"Dans la fonction : {resultat}")


calculer()
print(resultat)
Dans la fonction : 42
Traceback (most recent call last):
  File "<stdin>", line 6, in <module>
NameError: name 'resultat' is not defined

Analogie : une fonction est comme une pièce fermée avec un tableau blanc. Ce qu'on écrit sur le tableau à l'intérieur (les variables locales) est effacé en sortant. Pour faire sortir une information, il faut la passer par la porte : c'est return.

Les paramètres sont eux aussi des variables locales.

Lire une variable globale

Une variable créée au niveau principal du fichier (hors de toute fonction) est globale. Une fonction peut la lire :

TAUX_TVA = 0.20


def prix_ttc(prix_ht):
    return prix_ht * (1 + TAUX_TVA)


print(prix_ttc(100))   # → 120.0

Les constantes globales (valeurs qui ne changent jamais) s'écrivent par convention en MAJUSCULES.

Une affectation crée une variable locale

En revanche, si tu affectes une variable dans une fonction, Python crée une nouvelle variable locale, même si une globale du même nom existe :

compteur = 0


def incrementer():
    compteur = 100      # variable LOCALE, sans rapport avec la globale
    print(f"Dans la fonction : {compteur}")


incrementer()
print(f"Dehors : {compteur}")
Dans la fonction : 100
Dehors : 0

Et si tu essaies de modifier la globale avec +=, c'est l'erreur :

compteur = 0


def incrementer():
    compteur += 1


incrementer()
Traceback (most recent call last):
  File "<stdin>", line 8, in <module>
  File "<stdin>", line 5, in incrementer
UnboundLocalError: cannot access local variable 'compteur' where it is not associated with a value

Python a vu une affectation (+=) : il considère donc compteur comme locale… mais elle n'a pas encore de valeur.

Le mot-clé global (à éviter)

Le mot-clé global permet de dire « je veux modifier la variable globale » :

compteur = 0


def incrementer():
    global compteur
    compteur += 1


incrementer()
incrementer()
print(compteur)   # → 2

Ça marche, mais c'est une mauvaise habitude : n'importe quelle fonction peut modifier la variable, et on ne sait plus qui a fait quoi. La bonne pratique : passer les données en paramètres et récupérer les résultats avec return.

def incrementer(valeur):
    return valeur + 1


compteur = 0
compteur = incrementer(compteur)
compteur = incrementer(compteur)
print(compteur)   # → 2

Les objets modifiables passés en paramètre

Attention : si tu passes une liste ou un dictionnaire à une fonction, la fonction reçoit une étiquette vers le même objet (souviens-toi de la section « Références et copies » du chapitre 04). Si elle le modifie, la modification est visible à l'extérieur :

def ajouter_bonus(notes):
    notes.append(20)


mes_notes = [12, 15]
ajouter_bonus(mes_notes)
print(mes_notes)   # → [12, 15, 20]

Parfois c'est voulu (comme ici), parfois c'est un bug. Si la fonction ne doit pas toucher à l'original, elle doit travailler sur une copie ou construire une nouvelle liste qu'elle renvoie.


8. Documenter : les docstrings

Une docstring est une chaîne entre triples guillemets placée juste sous la ligne def. Elle explique ce que fait la fonction, ce qu'elle attend et ce qu'elle renvoie.

def convertir_temperature(celsius):
    """Convertit une température de degrés Celsius en degrés Fahrenheit.

    Paramètre :
        celsius : la température en °C (int ou float).

    Renvoie :
        La température en °F (float).
    """
    return celsius * 9 / 5 + 32


print(convertir_temperature(100))   # → 212.0
print(convertir_temperature(-40))   # → -40.0

Contrairement à un commentaire #, la docstring est attachée à la fonction : la fonction help() l'affiche, et ton éditeur (VS Code) la montre quand tu survoles le nom de la fonction.

def carre(nombre):
    """Renvoie le carré de nombre."""
    return nombre * nombre


help(carre)
Help on function carre in module __main__:

carre(nombre)
    Renvoie le carré de nombre.

Pour une fonction simple, une seule ligne suffit. Pour une fonction plus complexe, décris les paramètres et la valeur de retour. Une bonne docstring dit ce que fait la fonction, pas comment elle le fait.

Bonus : au chapitre 11, tu verras les annotations de type (def carre(nombre: float) -> float:), qui complètent la documentation.


9. Les fonctions sont des valeurs

En Python, une fonction est un objet comme un autre : on peut la ranger dans une variable, dans une liste, ou la passer en argument à une autre fonction. Attention à la différence entre carre (la fonction elle-même) et carre(3) (le résultat de son appel).

def carre(x):
    return x * x


def cube(x):
    return x * x * x


def appliquer(fonction, valeurs):
    resultats = []
    for valeur in valeurs:
        resultats.append(fonction(valeur))
    return resultats


print(appliquer(carre, [1, 2, 3]))   # → [1, 4, 9]
print(appliquer(cube, [1, 2, 3]))    # → [1, 8, 27]

Tu as déjà utilisé ce principe sans le savoir avec sorted(mots, key=len) : on passe la fonction len (sans parenthèses) à sorted, qui l'appelle elle-même sur chaque mot. On ira plus loin avec les fonctions lambda au chapitre 06.


10. La récursivité : une fonction qui s'appelle elle-même

Une fonction récursive est une fonction qui s'appelle elle-même. Cela paraît étrange, mais c'est très naturel pour certains problèmes.

Analogie : les poupées russes. Pour ouvrir toutes les poupées, tu ouvres la première… et à l'intérieur, tu as le même problème, en plus petit. Tu t'arrêtes quand la poupée ne s'ouvre plus : c'est le cas de base.

Exemple : la factorielle. 5! = 5 × 4 × 3 × 2 × 1. On remarque que 5! = 5 × 4!, et 4! = 4 × 3!, etc. Et par définition 1! = 1 (et 0! = 1).

def factorielle(n):
    if n <= 1:                         # cas de base : on s'arrête
        return 1
    return n * factorielle(n - 1)      # appel récursif sur un problème plus petit


print(factorielle(5))   # → 120
print(factorielle(10))  # → 3628800

Voici ce qui se passe pour factorielle(4) :

factorielle(4) = 4 * factorielle(3)
                     factorielle(3) = 3 * factorielle(2)
                                          factorielle(2) = 2 * factorielle(1)
                                                               factorielle(1) = 1
                                          factorielle(2) = 2 * 1 = 2
                     factorielle(3) = 3 * 2 = 6
factorielle(4) = 4 * 6 = 24

Une fonction récursive a toujours deux ingrédients :

  1. un cas de base qui renvoie un résultat sans s'appeler (sinon elle tourne à l'infini) ;
  2. un appel récursif sur un problème plus petit, qui se rapproche du cas de base.

Autre exemple : un compte à rebours.

def compte_a_rebours(n):
    if n == 0:
        print("Décollage !")
        return
    print(n)
    compte_a_rebours(n - 1)


compte_a_rebours(3)
3
2
1
Décollage !

La récursivité est idéale pour les structures « emboîtées » : des listes de listes de listes, des dossiers contenant des sous-dossiers… Exemple : additionner tous les nombres d'une liste imbriquée à n'importe quelle profondeur.

def somme_imbriquee(elements):
    total = 0
    for element in elements:
        if type(element) == list:
            total += somme_imbriquee(element)   # une sous-liste : même problème, plus petit
        else:
            total += element
    return total


print(somme_imbriquee([1, [2, 3], [4, [5, 6]], 7]))   # → 28

Si on oublie le cas de base, Python finit par abandonner :

def infini(n):
    return infini(n + 1)


infini(0)
Traceback (most recent call last):
  File "<stdin>", line 5, in <module>
  File "<stdin>", line 2, in infini
  File "<stdin>", line 2, in infini
  File "<stdin>", line 2, in infini
  [Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded

Python limite la profondeur à environ 1000 appels. Pour la plupart des problèmes simples (somme, factorielle sur de grands nombres…), une boucle reste préférable ; garde la récursivité pour les structures naturellement emboîtées.


11. Structurer un programme avec des fonctions

Quand ton programme grossit, découpe-le en fonctions qui ont chacune une seule responsabilité, et regroupe le programme principal dans une fonction main().

def demander_nombre(message):
    """Demande un entier positif à l'utilisateur jusqu'à obtenir une saisie valide."""
    saisie = input(message)
    while not saisie.isdigit():
        print("Merci d'entrer un nombre entier positif.")
        saisie = input(message)
    return int(saisie)


def calculer_prix(prix_unitaire, quantite, remise=0):
    """Renvoie le prix total après application d'une remise en pourcentage."""
    total = prix_unitaire * quantite
    return total * (1 - remise / 100)


def afficher_ticket(quantite, total):
    """Affiche un petit ticket de caisse."""
    print("-" * 20)
    print(f"Quantité : {quantite}")
    print(f"Total    : {total:.2f} €")
    print("-" * 20)


def main():
    quantite = demander_nombre("Combien de places ? ")
    remise = 10 if quantite >= 5 else 0
    total = calculer_prix(12.50, quantite, remise)
    afficher_ticket(quantite, total)


main()

Avec les saisies trois puis 6 :

Combien de places ? trois
Merci d'entrer un nombre entier positif.
Combien de places ? 6
--------------------
Quantité : 6
Total    : 67.50 €
--------------------

remise = 10 if quantite >= 5 else 0 est une expression conditionnelle (on dit aussi « ternaire ») : la valeur est 10 si la condition est vraie, 0 sinon. C'est un raccourci pour un if/else qui ne sert qu'à choisir une valeur.

Avantages de ce découpage :

  • main() se lit comme un résumé du programme ;
  • chaque fonction est courte et facile à vérifier ;
  • calculer_prix ne fait ni input ni print : elle est réutilisable partout et facile à tester.

Au chapitre 09, tu verras qu'on remplace souvent le simple appel main() par if __name__ == "__main__": main().


Pièges fréquents

Piège 1 — Oublier le return

def calculer_moyenne(notes):
    moyenne = sum(notes) / len(notes)


resultat = calculer_moyenne([10, 14])
print(resultat)          # → None

La fonction calcule bien la moyenne… puis la jette. Sans return, elle renvoie None. Et ça casse souvent plus loin :

def calculer_moyenne(notes):
    moyenne = sum(notes) / len(notes)


resultat = calculer_moyenne([10, 14])
print(resultat + 1)
Traceback (most recent call last):
  File "<stdin>", line 6, in <module>
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

Correction : ajouter return moyenne. Quand tu vois NoneType dans une erreur, pense tout de suite à un return oublié.

Piège 2 — Utiliser print au lieu de return

def ajouter_tva(prix):
    print(prix * 1.2)


total = ajouter_tva(10) + ajouter_tva(20)
12.0
24.0
Traceback (most recent call last):
  File "<stdin>", line 5, in <module>
TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'

Correction : return prix * 1.2, et faire le print à l'extérieur, là où on a besoin d'afficher.

Piège 3 — L'argument par défaut mutable

C'est un piège célèbre, même chez les développeurs expérimentés :

def ajouter_article(article, panier=[]):
    panier.append(article)
    return panier


print(ajouter_article("pain"))   # → ['pain']
print(ajouter_article("lait"))   # → ['pain', 'lait']

On s'attendait à ['lait'] ! La valeur par défaut [] est créée une seule fois, quand Python lit la ligne def. Tous les appels partagent donc la même liste, qui grossit d'appel en appel.

Correction : utiliser None comme valeur par défaut, et créer la liste dans la fonction.

def ajouter_article(article, panier=None):
    if panier is None:
        panier = []
    panier.append(article)
    return panier


print(ajouter_article("pain"))   # → ['pain']
print(ajouter_article("lait"))   # → ['lait']

Règle : jamais de liste, de dictionnaire ou d'ensemble comme valeur par défaut.

Piège 4 — Oublier les parenthèses à l'appel

def dire_bonjour():
    return "Bonjour"


message = dire_bonjour
print(message)

Sortie (l'adresse varie à chaque exécution) :

<function dire_bonjour at 0x7f3a2c1b8e00>

Sans parenthèses, on ne appelle pas la fonction : on manipule la fonction elle-même. Correction : message = dire_bonjour().

Piège 5 — Code placé après return

def diviser(a, b):
    return a / b
    print("Division effectuée")   # jamais exécuté !


print(diviser(10, 4))   # → 2.5

Tout ce qui suit un return (dans le même bloc) n'est jamais exécuté. Correction : placer le print avant le return, ou le supprimer.

Piège 6 — Modifier une variable globale sans le savoir… ou sans le pouvoir

Le code de la section 7 avec compteur += 1 dans une fonction provoque UnboundLocalError. Correction : passer la valeur en paramètre et la renvoyer (compteur = incrementer(compteur)) plutôt que d'utiliser global.

Piège 7 — Un return mal indenté dans une boucle

def contient_negatif(nombres):
    for nombre in nombres:
        if nombre < 0:
            return True
        return False      # MAUVAISE indentation : dans la boucle !


print(contient_negatif([3, 5, -2]))   # → False

La fonction renvoie False dès le premier élément positif, sans regarder les autres. Correction : le return False doit être après la boucle (au même niveau que le for), pour n'être atteint que si aucun négatif n'a été trouvé.

def contient_negatif(nombres):
    for nombre in nombres:
        if nombre < 0:
            return True
    return False


print(contient_negatif([3, 5, -2]))   # → True

Exercices

Exercice 1 — Fonctions simples ⭐

Écris : - aire_rectangle(longueur, largeur) qui renvoie l'aire ; - est_pair(n) qui renvoie True si n est pair, False sinon ; - plus_grand(a, b, c) qui renvoie le plus grand des trois nombres sans utiliser max.

Puis teste-les :

print(aire_rectangle(4, 2.5))   # 10.0
print(est_pair(7))              # False
print(est_pair(10))             # True
print(plus_grand(3, 9, 5))      # 9

Exercice 2 — Convertisseur avec valeur par défaut ⭐

Écris une fonction convertir(montant, taux=1.08, symbole="$") qui renvoie une chaîne affichant le montant converti avec 2 décimales et le symbole.

print(convertir(100))                     # 108.00 $
print(convertir(50, 0.85, "£"))           # 42.50 £
print(convertir(1000, symbole="CHF", taux=0.94))   # 940.00 CHF

Exercice 3 — Statistiques sur une liste ⭐⭐

Écris une fonction statistiques(nombres) qui renvoie un dictionnaire avec les clés "min", "max", "moyenne" (arrondie à 2 décimales avec round(valeur, 2)) et "mediane". La médiane est la valeur centrale de la liste triée ; si la liste a un nombre pair d'éléments, c'est la moyenne des deux valeurs centrales. La liste d'origine ne doit pas être modifiée.

notes = [12, 5, 18, 9, 14, 11]
print(statistiques(notes))
# {'min': 5, 'max': 18, 'moyenne': 11.5, 'mediane': 11.5}
print(statistiques([3, 1, 2]))
# {'min': 1, 'max': 3, 'moyenne': 2.0, 'mediane': 2}
print(notes)
# [12, 5, 18, 9, 14, 11]

Exercice 4 — Validateur de mot de passe ⭐⭐

Écris une fonction verifier_mot_de_passe(mdp) qui renvoie une liste de problèmes (liste vide si tout va bien). Règles : au moins 8 caractères, au moins un chiffre, au moins une majuscule. Les méthodes caractere.isdigit() et caractere.isupper() te seront utiles.

Écris ensuite une fonction afficher_verdict(mdp) qui utilise la première pour afficher le résultat.

afficher_verdict("abc")
afficher_verdict("motdepasse1")
afficher_verdict("MotDePasse2024")
'abc' refusé :
  - moins de 8 caractères
  - aucun chiffre
  - aucune majuscule
'motdepasse1' refusé :
  - aucune majuscule
'MotDePasse2024' accepté.

Exercice 5 — Somme flexible ⭐⭐

Écris une fonction somme_ponderee(*valeurs, **options) qui additionne toutes les valeurs. L'option coef (défaut 1) multiplie le total, et l'option arrondi (défaut 2) indique le nombre de décimales du résultat (avec round).

print(somme_ponderee(1, 2, 3))                    # 6
print(somme_ponderee(1.5, 2.25, coef=2))          # 7.5
print(somme_ponderee(10, 3.333, coef=1.5, arrondi=1))   # 20.0
print(somme_ponderee())                           # 0

Exercice 6 — Puissance et palindrome récursifs ⭐⭐

Écris deux fonctions récursives (sans boucle) : - puissance(base, exposant) qui calcule base ** exposant pour un exposant entier positif ou nul, en utilisant base ** n = base × base ** (n-1) et base ** 0 = 1 ; - est_palindrome(mot) qui renvoie True si le mot se lit pareil dans les deux sens. Indice : un mot de 0 ou 1 lettre est un palindrome ; sinon, il faut que la première et la dernière lettre soient égales et que mot[1:-1] (le mot sans ses extrémités) soit un palindrome.

print(puissance(2, 10))          # 1024
print(puissance(5, 0))           # 1
print(est_palindrome("radar"))   # True
print(est_palindrome("python"))  # False
print(est_palindrome("ressasser"))  # True

Exercice 7 — Gestion de bibliothèque ⭐⭐⭐

On représente une bibliothèque par une liste de dictionnaires :

livres = [
    {"titre": "Le Petit Prince", "auteur": "Saint-Exupéry", "annee": 1943, "dispo": True},
    {"titre": "1984", "auteur": "Orwell", "annee": 1949, "dispo": False},
    {"titre": "Vol de nuit", "auteur": "Saint-Exupéry", "annee": 1931, "dispo": True},
    {"titre": "La Ferme des animaux", "auteur": "Orwell", "annee": 1945, "dispo": True},
]

Écris les fonctions suivantes (aucune ne doit utiliser print, sauf la dernière) : - livres_de(livres, auteur) : renvoie la liste des titres de cet auteur ; - livres_disponibles(livres) : renvoie la liste des titres disponibles ; - emprunter(livres, titre) : si le livre existe et est disponible, le marque indisponible et renvoie True ; sinon renvoie False ; - plus_ancien(livres) : renvoie le dictionnaire du livre le plus ancien ; - afficher(livres) : affiche chaque livre au format [x] Titre (Auteur, année) ou [ ] s'il est emprunté.

print(livres_de(livres, "Orwell"))
print(emprunter(livres, "Vol de nuit"))
print(emprunter(livres, "Vol de nuit"))
print(emprunter(livres, "Dune"))
print(livres_disponibles(livres))
print(plus_ancien(livres)["titre"])
afficher(livres)
['1984', 'La Ferme des animaux']
True
False
False
['Le Petit Prince', 'La Ferme des animaux']
Vol de nuit
[x] Le Petit Prince (Saint-Exupéry, 1943)
[ ] 1984 (Orwell, 1949)
[ ] Vol de nuit (Saint-Exupéry, 1931)
[x] La Ferme des animaux (Orwell, 1945)

Exercice 8 — Aplatir une liste ⭐⭐⭐

Écris une fonction récursive aplatir(elements) qui transforme une liste imbriquée à n'importe quelle profondeur en une liste « plate », en conservant l'ordre.

print(aplatir([1, [2, 3], [4, [5, [6, 7]]], 8]))
# [1, 2, 3, 4, 5, 6, 7, 8]
print(aplatir([[["a"]], "b", []]))
# ['a', 'b']

Mini-projet — Le jeu du pendu, structuré en fonctions

Cahier des charges

Programme le célèbre jeu du pendu :

  • l'ordinateur choisit un mot secret dans une liste ;
  • le joueur propose une lettre à chaque tour ;
  • on affiche le mot avec les lettres trouvées et des _ pour les autres (ex. P _ T H _ N) ;
  • le joueur a droit à 6 erreurs ;
  • une lettre déjà proposée n'est pas comptée comme une erreur, mais on prévient le joueur ;
  • une saisie invalide (vide, plusieurs caractères, pas une lettre) est refusée ;
  • la partie se termine par une victoire ou une défaite, en révélant le mot.

Contrainte principale : le programme doit être découpé en fonctions courtes, chacune avec une docstring. Le programme principal se résume à un appel à main().

Pour choisir un mot au hasard, on utilise le module random (présenté en détail au chapitre 09) :

import random

mot = random.choice(["python", "fonction", "variable"])
print(mot in ["python", "fonction", "variable"])   # → True

Étapes suggérées

  1. choisir_mot(mots) : renvoie un mot au hasard, en majuscules.
  2. masquer(mot, lettres_trouvees) : renvoie la chaîne P _ T H _ N (indice : construis une liste de caractères puis assemble-la avec " ".join(liste), qu'on verra en détail au chapitre 06).
  3. demander_lettre(deja_proposees) : boucle jusqu'à obtenir une lettre valide (isalpha() et longueur 1) et pas déjà proposée ; renvoie la lettre en majuscule.
  4. mot_complet(mot, lettres_trouvees) : renvoie True si toutes les lettres du mot ont été trouvées.
  5. afficher_etat(...) : affiche le mot masqué, les erreurs restantes et les lettres déjà proposées.
  6. jouer_partie(mot) : la boucle de jeu ; renvoie True en cas de victoire.
  7. main() : choisit le mot, lance la partie, affiche le message final.

Astuce de test : pour tester ton programme sans hasard, appelle directement jouer_partie("PYTHON").

Pistes d'amélioration

  • Dessiner le pendu en ASCII art (une liste de 7 dessins, indexée par le nombre d'erreurs).
  • Proposer de rejouer à la fin et tenir un score (victoires / défaites).
  • Gérer les accents : considérer que E trouve aussi É, È, Ê.
  • Choisir un niveau de difficulté (longueur du mot, nombre d'erreurs autorisées).
  • Lire la liste des mots depuis un fichier texte (chapitre 07).

Corrigés

Corrigé de l'exercice 1
def aire_rectangle(longueur, largeur):
    """Renvoie l'aire d'un rectangle."""
    return longueur * largeur


def est_pair(n):
    """Renvoie True si n est pair."""
    return n % 2 == 0


def plus_grand(a, b, c):
    """Renvoie le plus grand des trois nombres."""
    maximum = a
    if b > maximum:
        maximum = b
    if c > maximum:
        maximum = c
    return maximum


print(aire_rectangle(4, 2.5))
print(est_pair(7))
print(est_pair(10))
print(plus_grand(3, 9, 5))
Sortie :
10.0
False
True
9
`est_pair` renvoie directement le résultat de la comparaison `n % 2 == 0`, qui est déjà un booléen : inutile d'écrire `if ...: return True else: return False`. Pour `plus_grand`, on part du principe que `a` est le plus grand et on corrige si on trouve mieux.
Corrigé de l'exercice 2
def convertir(montant, taux=1.08, symbole="$"):
    """Convertit un montant en euros dans une autre devise et renvoie une chaîne formatée."""
    converti = montant * taux
    return f"{converti:.2f} {symbole}"


print(convertir(100))
print(convertir(50, 0.85, "£"))
print(convertir(1000, symbole="CHF", taux=0.94))
Sortie :
108.00 $
42.50 £
940.00 CHF
Le troisième appel montre l'intérêt des arguments nommés : l'ordre n'importe plus. La fonction **renvoie** la chaîne au lieu de l'afficher : on pourrait ainsi l'écrire dans un fichier ou la combiner avec d'autres textes.
Corrigé de l'exercice 3
def mediane(nombres):
    """Renvoie la médiane d'une liste non vide de nombres."""
    tries = sorted(nombres)          # copie triée : l'original n'est pas touché
    n = len(tries)
    milieu = n // 2
    if n % 2 == 1:
        return tries[milieu]
    return (tries[milieu - 1] + tries[milieu]) / 2


def statistiques(nombres):
    """Renvoie un dictionnaire avec le min, le max, la moyenne et la médiane."""
    return {
        "min": min(nombres),
        "max": max(nombres),
        "moyenne": round(sum(nombres) / len(nombres), 2),
        "mediane": mediane(nombres),
    }


notes = [12, 5, 18, 9, 14, 11]
print(statistiques(notes))
print(statistiques([3, 1, 2]))
print(notes)
Sortie :
{'min': 5, 'max': 18, 'moyenne': 11.5, 'mediane': 11.5}
{'min': 1, 'max': 3, 'moyenne': 2.0, 'mediane': 2}
[12, 5, 18, 9, 14, 11]
On a extrait le calcul de la médiane dans sa propre fonction : chaque fonction fait une seule chose. `sorted()` (et non `.sort()`) garantit que la liste de l'appelant n'est pas modifiée. Pour 6 éléments triés `[5, 9, 11, 12, 14, 18]`, `milieu` vaut 3 : on fait la moyenne de `tries[2]` (11) et `tries[3]` (12).
Corrigé de l'exercice 4
def verifier_mot_de_passe(mdp):
    """Renvoie la liste des règles non respectées par mdp (vide si tout va bien)."""
    problemes = []
    if len(mdp) < 8:
        problemes.append("moins de 8 caractères")

    a_chiffre = False
    a_majuscule = False
    for caractere in mdp:
        if caractere.isdigit():
            a_chiffre = True
        if caractere.isupper():
            a_majuscule = True

    if not a_chiffre:
        problemes.append("aucun chiffre")
    if not a_majuscule:
        problemes.append("aucune majuscule")
    return problemes


def afficher_verdict(mdp):
    """Affiche si le mot de passe est accepté, et sinon pourquoi."""
    problemes = verifier_mot_de_passe(mdp)
    if len(problemes) == 0:
        print(f"'{mdp}' accepté.")
    else:
        print(f"'{mdp}' refusé :")
        for probleme in problemes:
            print(f"  - {probleme}")


afficher_verdict("abc")
afficher_verdict("motdepasse1")
afficher_verdict("MotDePasse2024")
Sortie :
'abc' refusé :
  - moins de 8 caractères
  - aucun chiffre
  - aucune majuscule
'motdepasse1' refusé :
  - aucune majuscule
'MotDePasse2024' accepté.
La séparation est importante : `verifier_mot_de_passe` **calcule** (et pourrait servir dans une application web), `afficher_verdict` **présente**. Les booléens `a_chiffre` et `a_majuscule` sont des « drapeaux » qui passent à `True` dès qu'on trouve un caractère correspondant.
Corrigé de l'exercice 5
def somme_ponderee(*valeurs, **options):
    """Additionne les valeurs, multiplie par l'option coef et arrondit."""
    coef = options.get("coef", 1)
    arrondi = options.get("arrondi", 2)
    total = sum(valeurs) * coef
    return round(total, arrondi)


print(somme_ponderee(1, 2, 3))
print(somme_ponderee(1.5, 2.25, coef=2))
print(somme_ponderee(10, 3.333, coef=1.5, arrondi=1))
print(somme_ponderee())
Sortie :
6
7.5
20.0
0
`valeurs` est un tuple (on peut l'envoyer directement à `sum`), `options` est un dictionnaire : `get` avec une valeur par défaut est donc parfait. Note que `round` appliqué à un entier renvoie un entier (`6`), et appliqué à un float renvoie un float. Dans un vrai programme, on écrirait plutôt `def somme_ponderee(*valeurs, coef=1, arrondi=2)` : des paramètres nommés explicites sont plus clairs que `**options`, mais l'exercice sert à manipuler `**kwargs`.
Corrigé de l'exercice 6
def puissance(base, exposant):
    """Calcule base ** exposant récursivement (exposant entier >= 0)."""
    if exposant == 0:                              # cas de base
        return 1
    return base * puissance(base, exposant - 1)    # problème plus petit


def est_palindrome(mot):
    """Renvoie True si mot se lit de la même façon dans les deux sens."""
    if len(mot) <= 1:                  # cas de base
        return True
    if mot[0] != mot[-1]:              # extrémités différentes : perdu
        return False
    return est_palindrome(mot[1:-1])   # on retire les extrémités et on recommence


print(puissance(2, 10))
print(puissance(5, 0))
print(est_palindrome("radar"))
print(est_palindrome("python"))
print(est_palindrome("ressasser"))
Sortie :
1024
1
True
False
True
Pour `"radar"` : `r == r`, on teste `"ada"` ; `a == a`, on teste `"d"` ; une seule lettre, c'est le cas de base, `True`. Chaque appel travaille sur un mot plus court de 2 lettres : on se rapproche forcément du cas de base.
Corrigé de l'exercice 7
def livres_de(livres, auteur):
    """Renvoie la liste des titres écrits par auteur."""
    titres = []
    for livre in livres:
        if livre["auteur"] == auteur:
            titres.append(livre["titre"])
    return titres


def livres_disponibles(livres):
    """Renvoie la liste des titres disponibles."""
    titres = []
    for livre in livres:
        if livre["dispo"]:
            titres.append(livre["titre"])
    return titres


def emprunter(livres, titre):
    """Marque le livre comme emprunté. Renvoie True si l'emprunt a réussi."""
    for livre in livres:
        if livre["titre"] == titre:
            if livre["dispo"]:
                livre["dispo"] = False
                return True
            return False          # trouvé mais déjà emprunté
    return False                  # pas trouvé du tout


def plus_ancien(livres):
    """Renvoie le dictionnaire du livre le plus ancien."""
    ancien = livres[0]
    for livre in livres:
        if livre["annee"] < ancien["annee"]:
            ancien = livre
    return ancien


def afficher(livres):
    """Affiche la liste des livres avec leur disponibilité."""
    for livre in livres:
        case = "x" if livre["dispo"] else " "
        print(f"[{case}] {livre['titre']} ({livre['auteur']}, {livre['annee']})")


livres = [
    {"titre": "Le Petit Prince", "auteur": "Saint-Exupéry", "annee": 1943, "dispo": True},
    {"titre": "1984", "auteur": "Orwell", "annee": 1949, "dispo": False},
    {"titre": "Vol de nuit", "auteur": "Saint-Exupéry", "annee": 1931, "dispo": True},
    {"titre": "La Ferme des animaux", "auteur": "Orwell", "annee": 1945, "dispo": True},
]

print(livres_de(livres, "Orwell"))
print(emprunter(livres, "Vol de nuit"))
print(emprunter(livres, "Vol de nuit"))
print(emprunter(livres, "Dune"))
print(livres_disponibles(livres))
print(plus_ancien(livres)["titre"])
afficher(livres)
Sortie :
['1984', 'La Ferme des animaux']
True
False
False
['Le Petit Prince', 'La Ferme des animaux']
Vol de nuit
[x] Le Petit Prince (Saint-Exupéry, 1943)
[ ] 1984 (Orwell, 1949)
[ ] Vol de nuit (Saint-Exupéry, 1931)
[x] La Ferme des animaux (Orwell, 1945)
`emprunter` **modifie** le dictionnaire reçu : comme la liste est passée par référence, la modification est visible après l'appel (c'est voulu ici). Note les deux `return False` : l'un quand le livre est trouvé mais indisponible, l'autre après la boucle quand il n'a pas été trouvé. On pourrait renvoyer des messages différents pour distinguer les deux cas.
Corrigé de l'exercice 8
def aplatir(elements):
    """Transforme une liste imbriquée en liste à un seul niveau."""
    resultat = []
    for element in elements:
        if type(element) == list:
            resultat.extend(aplatir(element))   # on aplatit la sous-liste, puis on ajoute ses éléments
        else:
            resultat.append(element)
    return resultat


print(aplatir([1, [2, 3], [4, [5, [6, 7]]], 8]))
print(aplatir([[["a"]], "b", []]))
Sortie :
[1, 2, 3, 4, 5, 6, 7, 8]
['a', 'b']
Le cas de base est implicite : un élément qui n'est pas une liste est simplement ajouté, et une liste vide ne déclenche aucun tour de boucle. Note l'usage d'`extend` (ajouter **les éléments** de la liste aplatie) et non d'`append` (qui ajouterait la liste entière, cf. piège 7 du chapitre 04).
Corrigé du mini-projet
import random

MOTS = ["python", "fonction", "variable", "boucle", "dictionnaire", "liste"]
ERREURS_MAX = 6


def choisir_mot(mots):
    """Renvoie un mot choisi au hasard, en majuscules."""
    return random.choice(mots).upper()


def masquer(mot, lettres_trouvees):
    """Renvoie le mot avec des _ à la place des lettres non trouvées."""
    affichage = []
    for lettre in mot:
        if lettre in lettres_trouvees:
            affichage.append(lettre)
        else:
            affichage.append("_")
    return " ".join(affichage)


def mot_complet(mot, lettres_trouvees):
    """Renvoie True si toutes les lettres du mot ont été trouvées."""
    for lettre in mot:
        if lettre not in lettres_trouvees:
            return False
    return True


def demander_lettre(deja_proposees):
    """Demande une lettre valide et pas encore proposée ; la renvoie en majuscule."""
    while True:
        saisie = input("Ta lettre : ").strip().upper()
        if len(saisie) != 1 or not saisie.isalpha():
            print("Tape une seule lettre.")
        elif saisie in deja_proposees:
            print(f"Tu as déjà proposé {saisie}.")
        else:
            return saisie


def afficher_etat(mot, lettres_trouvees, erreurs, deja_proposees):
    """Affiche le mot masqué, les erreurs restantes et les lettres déjà jouées."""
    print()
    print(masquer(mot, lettres_trouvees))
    print(f"Erreurs restantes : {ERREURS_MAX - erreurs}")
    if deja_proposees:
        print(f"Déjà proposées : {' '.join(sorted(deja_proposees))}")


def jouer_partie(mot):
    """Déroule une partie complète. Renvoie True si le joueur gagne."""
    lettres_trouvees = set()
    deja_proposees = set()
    erreurs = 0

    while erreurs < ERREURS_MAX:
        afficher_etat(mot, lettres_trouvees, erreurs, deja_proposees)
        lettre = demander_lettre(deja_proposees)
        deja_proposees.add(lettre)

        if lettre in mot:
            lettres_trouvees.add(lettre)
            print(f"Bien joué, {lettre} est dans le mot !")
            if mot_complet(mot, lettres_trouvees):
                return True
        else:
            erreurs += 1
            print(f"Raté, pas de {lettre}.")

    return False


def main():
    """Point d'entrée du jeu."""
    print("=== JEU DU PENDU ===")
    mot = choisir_mot(MOTS)
    if jouer_partie(mot):
        print(f"\nBravo ! Tu as trouvé le mot {mot}.")
    else:
        print(f"\nPerdu... Le mot était {mot}.")


main()
Pour vérifier le déroulement sans hasard, voici une partie avec le mot fixé (on remplace `main()` par l'appel direct ci-dessous) et les saisies `e`, `p`, `ab`, `y`, `p`, `t`, `h`, `o`, `n` :
# (à placer après les définitions précédentes, à la place de main())
if jouer_partie("PYTHON"):
    print("\nBravo !")
_ _ _ _ _ _
Erreurs restantes : 6
Ta lettre : e
Raté, pas de E.

_ _ _ _ _ _
Erreurs restantes : 5
Déjà proposées : E
Ta lettre : p
Bien joué, P est dans le mot !

P _ _ _ _ _
Erreurs restantes : 5
Déjà proposées : E P
Ta lettre : ab
Tape une seule lettre.
Ta lettre : y
Bien joué, Y est dans le mot !

P Y _ _ _ _
Erreurs restantes : 5
Déjà proposées : E P Y
Ta lettre : p
Tu as déjà proposé P.
Ta lettre : t
Bien joué, T est dans le mot !

P Y T _ _ _
Erreurs restantes : 5
Déjà proposées : E P T Y
Ta lettre : h
Bien joué, H est dans le mot !

P Y T H _ _
Erreurs restantes : 5
Déjà proposées : E H P T Y
Ta lettre : o
Bien joué, O est dans le mot !

P Y T H O _
Erreurs restantes : 5
Déjà proposées : E H O P T Y
Ta lettre : n
Bien joué, N est dans le mot !

Bravo !
**Points clés de la solution :** - Chaque fonction a **une seule responsabilité** et une docstring ; `main()` se lit comme le scénario du jeu. - `masquer` et `mot_complet` ne font aucun `print` ni `input` : ce sont des fonctions « pures », faciles à tester (`masquer("PYTHON", {"P", "N"})` doit renvoyer `"P _ _ _ _ N"`). - Les lettres sont stockées dans des **ensembles** (chapitre 04) : pas de doublon, test `in` rapide. - `demander_lettre` utilise `while True` + `return` : la fonction ne rend la main qu'avec une saisie valide. - Les constantes `MOTS` et `ERREURS_MAX` sont en majuscules et en haut du fichier : pour changer la difficulté, une seule ligne à modifier. - Pouvoir appeler `jouer_partie("PYTHON")` directement montre l'avantage d'avoir séparé le choix du mot du déroulement de la partie.

Récapitulatif

  • Une fonction se définit avec def nom(parametres): et s'appelle avec nom(arguments). Elle doit être définie avant d'être appelée.
  • return renvoie une valeur au code appelant et arrête la fonction. Sans return, une fonction renvoie None.
  • print montre à l'humain ; return donne au programme. Une fonction de calcul doit renvoyer son résultat.
  • On peut renvoyer plusieurs valeurs (un tuple) et les déballer : a, b = f().
  • Les valeurs par défaut (def f(x, y=0)) rendent des paramètres optionnels ; les arguments nommés (f(x=1, y=2)) rendent les appels lisibles.
  • *args regroupe les arguments positionnels en trop dans un tuple, **kwargs les arguments nommés en trop dans un dictionnaire.
  • Les variables créées dans une fonction sont locales. Évite global : passe les données en paramètres et renvoie les résultats.
  • Une liste passée en paramètre peut être modifiée par la fonction. Jamais de valeur par défaut mutable : utilise None.
  • Une docstring documente la fonction juste sous le def.
  • Une fonction récursive s'appelle elle-même ; elle a besoin d'un cas de base et d'un appel sur un problème plus petit.

Checklist de validation

  • Je sais écrire une fonction avec paramètres qui renvoie un résultat.
  • Je sais expliquer la différence entre print et return.
  • Je sais utiliser une valeur par défaut et appeler une fonction avec des arguments nommés.
  • Je connais le piège de l'argument par défaut mutable et sa correction.
  • Je sais pourquoi une variable créée dans une fonction n'existe pas à l'extérieur.
  • J'écris une docstring pour chacune de mes fonctions.
  • Je sais écrire une fonction récursive avec un cas de base.
  • J'ai terminé le pendu en le découpant en fonctions.

← Chapitre précédent | Sommaire | Chapitre suivant →