Aller au contenu

Les fonctions — l'abstraction procédurale

Regarde ce programme de jeu de devinettes, vu dans le chapitre sur les boucles :

import random
print("=== JEU DE DEVINETTES ===")
print("Je pense à un nombre entre 1 et 100.")
print()
secret = random.randint(1, 100)
tentatives = 0
while True:
reponse = int(input("Ta proposition : "))
tentatives = tentatives + 1
if reponse < secret:
print("Trop petit !")
elif reponse > secret:
print("Trop grand !")
else:
print("Bravo ! Tu as trouvé", secret, "en", tentatives, "tentatives !")
break
if tentatives <= 3:
print("Incroyable !")
elif tentatives <= 7:
print("Bien joué !")
else:
print("Tu peux faire mieux !")

Ce programme fait plusieurs choses distinctes :

  1. Afficher un en-tête (lignes 3–4)
  2. Générer le nombre secret (ligne 7)
  3. Jouer la partie — la boucle de devinettes (lignes 10–19)
  4. Évaluer la performance (lignes 21–26)

Chacun de ces blocs est un sous-programme : un morceau de code qui accomplit une tâche précise. Pour l’instant, ils sont tous mélangés dans un seul fichier. Mais que se passe-t-il si tu veux :

  • Rejouer une partie sans relancer le programme ?
  • Réutiliser l’évaluation de performance dans un autre jeu ?
  • Tester la boucle de devinettes indépendamment du reste ?

C’est là qu’interviennent les fonctions : elles permettent de découper un programme en sous-programmes nommés, réutilisables et indépendants.

Voici d’autres exemples, tirés des chapitres précédents, où certaines parties du code pourrait être isolé en sous-programme :

Un calcul réutilisable — l’IMC :

# Un calcul réutilisable — l'IMC
imc = poids / taille ** 2

Une validation de saisie :

# Une validation de saisie — patron qui se répète
while True:
note = float(input("Note (0 à 100) : "))
if note >= 0 and note <= 100:
break
print("Erreur !")

Un traitement dans un menu :

# Chaque option du menu est un sous-programme potentiel
match choix:
case "1":
km = float(input("Distance en km : "))
milles = km * 0.621371
print(format(km, ".2f"), "km =", format(milles, ".2f"), "milles")
case "2":
# ...

Une formule scientifique :

# Une formule scientifique — Héron
import math
s = (a + b + c) / 2
aire = math.sqrt(s * (s - a) * (s - b) * (s - c))

Dans chaque cas : un bloc cohérent qui prend des données, effectue un traitement, et produit un résultat. C’est exactement la définition d’une fonction.


def nom_de_la_fonction(parametre1, parametre2):
instructions
return resultat
  1. Le mot-clé def (abréviation de define)
  2. Le nom de la fonction — suit les mêmes règles qu’un nom de variable
  3. Les parenthèses contenant les paramètres (ou vides si aucun)
  4. Les deux-points :
  5. Le corps de la fonction — bloc indenté d’instructions
  6. Le return (optionnel) — la valeur renvoyée à l’appelant
def saluer(prenom):
print("Bonjour", prenom, "!")
saluer("Alice")
saluer("Bob")
Bonjour Alice !
Bonjour Bob !

La fonction saluer encapsule un comportement. On l’appelle autant de fois qu’on veut, avec des arguments différents. Le code à l’intérieur n’est exécuté que lors de l’appel — la définition seule ne fait rien.

def calculer_imc(poids, taille):
imc = poids / taille ** 2
return imc
# Utilisation (appel de la fonction `calculer_imc`)
mon_imc = calculer_imc(70, 1.75)
print("Ton IMC est :", format(mon_imc, ".2f"))
Ton IMC est : 22.86

Le return renvoie le résultat au code appelant. Ce résultat peut être stocké dans une variable, utilisé dans une expression, ou passé à une autre fonction :

# Stocker le résultat
mon_imc = calculer_imc(70, 1.75)
# Utiliser directement dans un print
print("IMC :", format(calculer_imc(70, 1.75), ".2f"))
# Utiliser dans une condition
if calculer_imc(70, 1.75) > 25:
print("Surpoids")

Comme mentionné plus haut, une fonction ne retourne pas obligatoirement une valeur. Si elle n’a pas de return (ou a un return sans valeur), elle retourne None :

def afficher_en_tete(titre):
print("=" * 30)
print(titre)
print("=" * 30)
resultat = afficher_en_tete("MON PROGRAMME")
print(resultat) # None

Les paramètres d’une fonction servent à permettre à la fonction de recevoir des informations (valeurs) dont elle a besoin pour effectuer une tâche. Les paramètres sont des variables déclarées dans la définition d’une fonction. Ils permettent de transmettre des données à la fonction au moment où on l’utilise (l’appel de la fonction).

Grâce aux paramètres, une même fonction peut être utilisée avec des valeurs différentes, ce qui la rend plus flexible et réutilisable.

Par défaut, tous les paramètres sont obligatoires. L’appel doit fournir exactement le bon nombre d’arguments :

def calculer_imc(poids, taille):
return poids / taille ** 2
calculer_imc(70, 1.75) # ✅ 2 arguments pour 2 paramètres
calculer_imc(70) # ❌ TypeError: missing 1 required positional argument
calculer_imc(70, 1.75, 25) # ❌ TypeError: takes 2 positional arguments but 3 were given

On peut donner une valeur par défaut à un paramètre. Si l’argument n’est pas fourni, la valeur par défaut est utilisée :

def saluer(prenom, formule="Bonjour"):
print(formule, prenom, "!")
saluer("Alice") # Utilise "Bonjour" par défaut
saluer("Bob", "Bonsoir") # Remplace la valeur par défaut

Le paramètre formule est optionnel : il a une valeur par défaut ("Bonjour"), donc on peut l’omettre à l’appel.

Bonjour Alice !
Bonsoir Bob !

Les paramètres avec valeur par défaut doivent être placés après les paramètres obligatoires :

# ✅ Obligatoires d'abord, optionnels ensuite
def creer_facture(montant, taxe=0.14975, rabais=0):
total = montant * (1 + taxe) - rabais
return round(total, 2)
# ❌ SyntaxError
def creer_facture(taxe=0.14975, montant):
...
creer_facture(100) # montant=100, taxe=0.14975, rabais=0
creer_facture(100, 0.05) # montant=100, taxe=0.05, rabais=0
creer_facture(100, 0.14975, 10) # montant=100, taxe=0.14975, rabais=10

Une variable créée à l’intérieur d’une fonction n’existe que pendant l’exécution de cette fonction. C’est une variable locale :

def calculer_aire(rayon):
import math
aire = math.pi * rayon ** 2
return aire
calculer_aire(5)
print(aire) # ❌ NameError: name 'aire' is not defined

La variable aire naît quand la fonction s’exécute et disparaît quand elle se termine.

Les paramètres sont aussi des variables locales :

def doubler(nombre):
nombre = nombre * 2 # modifie la copie locale
return nombre
x = 5
resultat = doubler(x)
print(resultat) # 10
print(x) # 5 — x n'a pas changé !

Quand tu passes x à doubler(), Python crée une copie locale appelée nombre. Modifier nombre dans la fonction ne touche pas x à l’extérieur.

Une variable créée en dehors de toute fonction est une variable globale. Elle est accessible en lecture partout, y compris dans les fonctions :

NOM_DU_PROGRAMME = "Calculatrice" # Variable globale
def afficher_en_tete():
print("===", NOM_DU_PROGRAMME, "===") # Lecture d'une variable globale
afficher_en_tete()

Cependant, une fonction ne peut pas modifier une variable globale sans le déclarer explicitement :

compteur = 0
def incrementer():
compteur = compteur + 1 # ❌ UnboundLocalError

Ici, Python pense qu’on crée une variable locale compteur, mais on essaie de lire compteur avant de lui assigner une valeur

La seule bonne raison d’utiliser des globales dans les fonctions : les constantes (valeurs qui ne changent jamais).

TAUX_TPS = 0.05
TAUX_TVQ = 0.09975
def calculer_taxes(montant):
tps = round(montant * TAUX_TPS, 2)
tvq = round(montant * TAUX_TVQ, 2)
return tps + tvq

Convention Python : les constantes sont en MAJUSCULES. La fonction les lit, mais ne les modifie jamais.


Chaque fonction devrait remplir une seule tâche. Son nom devrait la décrire.

Problème: Ici analyser_patient FAIT TROP de choses : calcule, catégorise ET affiche

def analyser_patient(poids, taille):
imc = poids / taille ** 2
if imc < 18.5:
categorie = "Insuffisance"
elif imc < 25:
categorie = "Normal"
elif imc < 30:
categorie = "Surpoids"
else:
categorie = "Obésité"
print("IMC :", format(imc, ".2f"))
print("Catégorie :", categorie)

Solution: Définir des fonctions qui font chacune UNE chose:

def calculer_imc(poids, taille):
return poids / taille ** 2
def categorie_imc(imc):
if imc < 18.5:
return "Insuffisance pondérale"
elif imc < 25:
return "Poids normal"
elif imc < 30:
return "Surpoids"
else:
return "Obésité"

Une fonction ne devrait pas dépendre de variables définies ailleurs (sauf les constantes globales). Tout ce dont elle a besoin doit être passé en paramètres.

Problème: Ici calculer_imc() dépend de variables globales.

poids = 70
taille = 1.75
def calculer_imc():
return poids / taille ** 2

Solution: Ici calculer_imc() est autonome.

poids = 70
taille = 1.75
# Reçoit tout en paramètres
def calculer_imc(poids, taille):
return poids / taille ** 2

Si une fonction a besoin de 8 paramètres, elle fait probablement trop de choses. Découpe-la.

Problème: Trop de paramètres — signal d’alarme

def generer_rapport(nom, prenom, age, note1, note2, note3, programme, session):
...

Solution: Découper en fonctions plus petites

def calculer_moyenne(note1, note2, note3):
return (note1 + note2 + note3) / 3
def formater_nom(nom, prenom):
return prenom + " " + nom

Le nom d’une fonction devrait être un verbe qui décrit ce qu’elle fait :

# ✅ Noms descriptifs
def calculer_imc(poids, taille):
...
def valider_note(note):
...
def afficher_menu():
...
def convertir_celsius_en_fahrenheit(celsius):
...
# ❌ Noms vagues ou trompeurs
def faire_truc(x, y):
...
def imc(p, t): # imc est un nom, pas un verbe
...
def f(a, b, c): # incompréhensible
...
PrincipeExplication
Une seule tâcheLa fonction fait une chose et la fait bien
AutonomeNe dépend pas de variables globales non constantes
Paramètres explicitesTout ce dont elle a besoin est passé en paramètres
Retourner plutôt qu’afficherFonctions de calcul → return ; print dans l’appelant
Peu de paramètres3–4 maximum ; au-delà, découper
Nom descriptifVerbe + complément décrivant la tâche

Reprenons le programme du début, découpé en fonctions :

import random
# --- Fonctions ---
def afficher_en_tete():
print("=== JEU DE DEVINETTES ===")
print("Je pense à un nombre entre 1 et 100.")
print()
def jouer_partie(secret):
tentatives = 0
while True:
reponse = int(input("Ta proposition : "))
tentatives = tentatives + 1
if reponse < secret:
print("Trop petit !")
elif reponse > secret:
print("Trop grand !")
else:
print("Bravo ! Tu as trouvé", secret, "en", tentatives, "tentatives !")
return tentatives
def evaluer_performance(tentatives):
if tentatives <= 3:
return "Incroyable !"
elif tentatives <= 7:
return "Bien joué !"
else:
return "Tu peux faire mieux !"
# --- Programme principal ---
afficher_en_tete()
secret = random.randint(1, 100)
tentatives = jouer_partie(secret)
appreciation = evaluer_performance(tentatives)
print(appreciation)

Le programme principal se lit maintenant comme un plan : afficher l’en-tête → générer le secret → jouer → évaluer → afficher.

Chaque fonction est testable et réutilisable indépendamment. Et ajouter une boucle « Rejouer ? » devient trivial :

# --- Programme principal ---
afficher_en_tete()
while True:
secret = random.randint(1, 100)
tentatives = jouer_partie(secret)
print(evaluer_performance(tentatives))
encore = input("Rejouer ? (oui/non) : ")
if encore != "oui":
print("Merci d'avoir joué !")
break

Sans fonctions, ajouter cette boucle aurait demandé de restructurer tout le programme. Avec des fonctions, c’est quelques lignes de plus.


Pense à un programme Python complet — par exemple, le jeu de devinettes. Il a :

  • Des entrées : rien (ou des arguments en ligne de commande)
  • Un traitement : la logique du jeu
  • Des effets : affichage, saisie

C’est exactement la même structure qu’une fonction. En fait, on pourrait envelopper tout le programme dans une fonction :

import random
def jeu_devinettes(minimum=1, maximum=100):
print("=== JEU DE DEVINETTES ===")
print("Je pense à un nombre entre", minimum, "et", maximum)
print()
secret = random.randint(minimum, maximum)
tentatives = 0
while True:
reponse = int(input("Ta proposition : "))
tentatives = tentatives + 1
if reponse < secret:
print("Trop petit !")
elif reponse > secret:
print("Trop grand !")
else:
print("Bravo ! Trouvé en", tentatives, "tentatives !")
return tentatives
# Le programme entier devient un appel paramétrable
jeu_devinettes() # Partie classique (1 à 100)
jeu_devinettes(1, 10) # Partie facile
jeu_devinettes(1, 1000) # Partie difficile

Le programme est maintenant paramétrable : on change la difficulté sans modifier le code. Dans un projet plus grand, ce fichier pourrait être importé comme un module et appelé par un autre programme.