20  Les fonctions

Les fonctions sont l’outil #1 pour organiser et réutiliser son code. Au niveau Basique, on utilisait des fonctions natives (len, sum, sorted…). Au niveau Opérationnel, vous allez créer les vôtres. C’est la différence entre « savoir utiliser Python » et « coder en Python ».

20.1 Pourquoi des fonctions ?

Imaginez devoir calculer la moyenne de 3 listes de notes différentes. Sans fonction :

notes_alice = [12, 15, 14, 16]
notes_bob = [11, 10, 9, 8]
notes_charlie = [18, 17, 19, 16]

print("Alice :", sum(notes_alice) / len(notes_alice))
print("Bob   :", sum(notes_bob) / len(notes_bob))
print("Charlie :", sum(notes_charlie) / len(notes_charlie))
Alice : 14.25
Bob   : 9.5
Charlie : 17.5

On répète la même logique (sum(...) / len(...)) trois fois. Avec une fonction :

def moyenne(notes):
    return sum(notes) / len(notes)

print("Alice :", moyenne([12, 15, 14, 16]))
print("Bob   :", moyenne([11, 10, 9, 8]))
print("Charlie :", moyenne([18, 17, 19, 16]))
Alice : 14.25
Bob   : 9.5
Charlie : 17.5

Les fonctions apportent trois bénéfices majeurs :

  1. Réutilisation : on écrit la logique une fois, on l’utilise N fois.
  2. Lisibilité : moyenne(notes) est plus parlant que sum(notes)/len(notes).
  3. Maintenance : si la formule change, on corrige un seul endroit.

20.2 Définir et appeler une fonction

Syntaxe de définition

def nom_fonction(parametres):
    """Docstring (optionnelle mais recommandée)."""
    # corps de la fonction
    return valeur       # optionnel

Premier exemple

def saluer():
    print("Bonjour TOSA !")

# Appel
saluer()
saluer()
Bonjour TOSA !
Bonjour TOSA !

Décomposition :

  • def : mot-clé qui annonce une définition de fonction.
  • saluer : nom de la fonction (en snake_case).
  • () : parenthèses — obligatoires, même sans paramètre.
  • : : deux-points (comme pour if, for, while).
  • indentation : le bloc appartient à la fonction.
Définir n’est pas exécuter

Quand Python lit def saluer(): ..., il mémorise la fonction mais ne l’exécute PAS. C’est seulement l’appel saluer() qui l’exécute.

def f():
    print("Coucou")

# Ici, "Coucou" ne s'affiche pas encore
print("Avant l'appel")

f()   # Maintenant ça s'affiche
Avant l'appel
Coucou

Les parenthèses () sont ce qui déclenche l’appel. Sans elles, on ne fait que référencer la fonction :

def f():
    print("Coucou")

print(f)       # affiche l'objet fonction, ne l'exécute pas
f()            # exécute
<function f at 0x000001D84E701080>
Coucou

20.3 Les paramètres

Les paramètres permettent de passer des données à la fonction.

def saluer(prenom):
    print("Bonjour", prenom)

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

Plusieurs paramètres, séparés par des virgules :

def additionner(a, b):
    resultat = a + b
    print(resultat)

additionner(3, 5)
additionner(10, 20)
8
30

Vocabulaire précis : paramètre vs argument

Terme Signification
Paramètre Variable définie dans la signature de la fonction (def f(x):)
Argument Valeur réellement passée lors de l’appel (f(5))
def multiplier(a, b):   # a et b sont des paramètres
    return a * b

r = multiplier(3, 5)    # 3 et 5 sont des arguments

C’est un détail, mais le TOSA peut tester le vocabulaire.

20.4 L’instruction return

return renvoie une valeur à l’appelant et sort immédiatement de la fonction.

def carre(x):
    return x * x

r = carre(5)
print(r)
print(carre(10) + carre(3))
25
109

Différence fondamentale entre print et return

⚠️ Piège TOSA très classique

print affiche à l’écran ; return renvoie une valeur exploitable.

def afficher(x):
    print(x * 2)        # print, pas return

def calculer(x):
    return x * 2        # return, pas print

a = afficher(5)          # affiche 10, mais a vaut None !
b = calculer(5)          # n'affiche rien, mais b vaut 10

print("a =", a)
print("b =", b)
10
a = None
b = 10

Question TOSA type : « Que renvoie afficher(5) ? » → None (pas 10 !).

Une fonction peut avoir plusieurs return

Utile pour les conditions :

def valeur_absolue(x):
    if x >= 0:
        return x
    else:
        return -x

print(valeur_absolue(5))
print(valeur_absolue(-7))
5
7

Une fonction peut ne rien returner

Dans ce cas, elle renvoie implicitement None :

def bonjour(nom):
    print("Bonjour", nom)
    # pas de return

resultat = bonjour("Alice")
print("resultat =", resultat)      # None
Bonjour Alice
resultat = None

Retour multiple (tuple implicite)

On peut renvoyer plusieurs valeurs séparées par des virgules — c’est en fait un tuple.

def min_max(nombres):
    return min(nombres), max(nombres)

result = min_max([3, 1, 4, 1, 5, 9, 2, 6])
print(result)
print(type(result))

# Déballage direct
petit, grand = min_max([3, 1, 4, 1, 5, 9, 2, 6])
print("Min:", petit, "Max:", grand)
(1, 9)
<class 'tuple'>
Min: 1 Max: 9

20.5 Les paramètres par défaut

On peut donner une valeur par défaut à un paramètre. L’appelant peut alors l’omettre.

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

saluer("Alice")                   # utilise la valeur par défaut
saluer("Bob", "Salut")             # remplace la valeur par défaut
Bonjour, Alice !
Salut, Bob !
Règle de placement

Les paramètres avec valeur par défaut doivent venir après les paramètres sans défaut dans la signature.

# ❌ Erreur : on ne peut pas avoir "b" obligatoire après "a" optionnel
def f(a=1, b):
    pass
  Cell In[14], line 2
    def f(a=1, b):
               ^
SyntaxError: parameter without a default follows parameter with a default
# ✅ Ordre correct
def f(a, b=1):
    pass
⚠️ Piège TOSA majeur : valeurs par défaut MUTABLES

N’utilisez JAMAIS une liste, un dict ou un set comme valeur par défaut !

# ❌ DANGEREUX
def ajouter(element, liste=[]):
    liste.append(element)
    return liste

print(ajouter("a"))      # ['a']
print(ajouter("b"))      # ['a', 'b'] !?! surprise
print(ajouter("c"))      # ['a', 'b', 'c']
['a']
['a', 'b']
['a', 'b', 'c']

Le défaut [] est créé une seule fois à la définition, puis partagé entre tous les appels. La bonne pratique :

# ✅ Pattern correct
def ajouter(element, liste=None):
    if liste is None:
        liste = []
    liste.append(element)
    return liste

print(ajouter("a"))
print(ajouter("b"))      # liste neuve à chaque appel
['a']
['b']

Ce piège est très testé au TOSA Avancé. Nous le reverrons au chapitre 5.

20.6 Les arguments nommés (keyword arguments)

À l’appel, on peut passer les arguments par position ou par nom :

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

# Par position
saluer("Alice")

# Par nom (keyword argument)
saluer(prenom="Alice", politesse="Salut")

# Par nom, on peut changer l'ordre !
saluer(politesse="Hello", prenom="Bob")

# Mélange possible : positionnels d'abord, puis nommés
saluer("Charlie", ponctuation="?")
Bonjour Alice!
Salut Alice!
Hello Bob!
Bonjour Charlie?
Quand utiliser les arguments nommés ?
  • Quand un appel a beaucoup de paramètres et qu’on veut être explicite.
  • Quand on veut sauter un paramètre optionnel au milieu.
  • Pour améliorer la lisibilité :
# ❌ Peu clair à la lecture
creer_utilisateur("Alice", 30, True, False)

# ✅ Bien plus clair
creer_utilisateur(nom="Alice", age=30, actif=True, admin=False)

20.7 La portée des variables (scope)

La portée (scope) détermine une variable est accessible.

Variable locale

Une variable définie dans une fonction est locale à cette fonction. Elle n’existe pas à l’extérieur.

def f():
    x = 10           # locale à f
    print("Dans f : x =", x)

f()
Dans f : x = 10
def f():
    x = 10

f()
print(x)            # ❌ NameError : x n'existe pas ici
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[19], line 5
      1 def f():
      2     x = 10
      3 
      4 f()
----> 5 print(x)            # ❌ NameError : x n'existe pas ici

NameError: name 'x' is not defined

Variable globale

Une variable définie à l’extérieur d’une fonction est globale — accessible en lecture depuis une fonction.

message = "Bonjour !"     # globale

def afficher():
    print(message)         # on peut LIRE la globale

afficher()
Bonjour !
⚠️ Modifier une globale depuis une fonction

Par défaut, impossible. Python crée une nouvelle variable locale :

compteur = 0

def increment():
    compteur = compteur + 1      # ❌ UnboundLocalError en pratique

Pour vraiment modifier la globale, il faut le déclarer explicitement avec globalmais c’est rarement souhaitable.

compteur = 0

def increment():
    global compteur            # on DIT qu'on modifie la globale
    compteur += 1

increment()
increment()
print(compteur)
2

Bonne pratique : évitez les globales modifiées. Passez la valeur en paramètre et retournez-en une nouvelle :

def incrementer(c):
    return c + 1

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

20.8 Fonction vs procédure

Certains langages distinguent fonction (renvoie une valeur) et procédure (sans retour). Python ne fait pas cette distinction — tout est une fonction. Une fonction sans return renvoie implicitement None.

def afficher(x):        # "procédure" — pas de return
    print(x)

def doubler(x):         # "fonction" — renvoie une valeur
    return 2 * x

print(afficher(5))      # affiche 5 puis None
print(doubler(5))       # affiche 10
5
None
10

20.9 Conventions de nommage (PEP 8)

  • Noms en snake_case : mots en minuscules séparés par _.
  • Verbes d’action pour décrire ce que fait la fonction.
  • Nom descriptif même au prix de quelques caractères.
# ✅ Bons noms
def calculer_moyenne(notes): ...
def est_majeur(age): ...
def envoyer_email(destinataire, sujet): ...

# ❌ À éviter
def calcMoy(n): ...        # camelCase, cryptique
def f(x): ...               # nom non parlant
def CALCULER(x): ...        # RÉSERVÉ AUX CONSTANTES

🧩 Quiz 1.1 — Les fonctions

Question 1

Que renvoie f(5) pour cette fonction ?

def f(x):
    x * 2
  1. 10
  2. 5
  3. None
  4. Une erreur

c) None — piège classique ! La ligne x * 2 calcule mais ne renvoie rien (pas de return). La fonction renvoie donc None par défaut.

def f(x):
    x * 2

print(f(5))
None

Pour renvoyer le résultat, il faut return x * 2.

Question 2

Que s’affiche-t-il ?

def saluer(nom="TOSA", politesse="Bonjour"):
    print(politesse, nom)

saluer(politesse="Salut")
  1. Salut TOSA
  2. Bonjour Salut
  3. Bonjour TOSA
  4. Une erreur

a) Salut TOSAnom prend sa valeur par défaut ("TOSA") et politesse est passée explicitement ("Salut").

def saluer(nom="TOSA", politesse="Bonjour"):
    print(politesse, nom)

saluer(politesse="Salut")
Salut TOSA

Question 3

Quelle affirmation est VRAIE ?

  1. print et return sont synonymes
  2. print affiche, return renvoie une valeur utilisable
  3. Une fonction doit toujours avoir un return
  4. return peut apparaître plusieurs fois dans une fonction, mais seul le premier est exécuté par appel

b) et d) sont vraies. Mais la plus précise est d) : on peut avoir plusieurs return, mais dès qu’un est atteint, la fonction sort. Les autres ne sont pas exécutés pour cet appel.

def signe(x):
    if x > 0:
        return "positif"
    elif x < 0:
        return "négatif"
    return "nul"           # atteint seulement si x == 0

print(signe(5), signe(-3), signe(0))
positif négatif nul

Question 4

Que s’affiche-t-il ?

x = 10

def f():
    x = 20
    print(x)

f()
print(x)
  1. 20 puis 20
  2. 20 puis 10
  3. 10 puis 20
  4. 10 puis 10

b) 20 puis 10 — dans f(), on crée une variable locale x qui n’a rien à voir avec la globale. La globale reste à 10.

x = 10

def f():
    x = 20
    print(x)

f()
print(x)
20
10

Question 5

Quelle définition est invalide ?

  1. def f(a, b=1):
  2. def f(a=1, b=2):
  3. def f(a=1, b):
  4. def f(a, b):

c) def f(a=1, b): — les paramètres avec défaut doivent venir après les paramètres sans défaut.

def f(a=1, b):
    pass
  Cell In[29], line 1
    def f(a=1, b):
               ^
SyntaxError: parameter without a default follows parameter with a default

Question 6

Que renvoie min_max([3, 1, 4]) ?

def min_max(nombres):
    return min(nombres), max(nombres)
  1. (1, 4)
  2. [1, 4]
  3. 1 puis 4
  4. 1

a) (1, 4) — les retours multiples sont emballés dans un tuple implicite.

def min_max(nombres):
    return min(nombres), max(nombres)

print(min_max([3, 1, 4]))
print(type(min_max([3, 1, 4])))
(1, 4)
<class 'tuple'>

Question 7

Que s’affiche-t-il ?

def ajouter(x, liste=[]):
    liste.append(x)
    return liste

print(ajouter(1))
print(ajouter(2))
  1. [1] puis [2]
  2. [1] puis [1, 2]
  3. Une erreur
  4. [1, 2] puis [1, 2]

b) [1] puis [1, 2] — piège des valeurs par défaut mutables ! La liste [] est créée une seule fois et partagée entre tous les appels.

def ajouter(x, liste=[]):
    liste.append(x)
    return liste

print(ajouter(1))
print(ajouter(2))
[1]
[1, 2]

Règle : ne jamais utiliser une liste (ou dict/set) comme valeur par défaut. Utilisez None et testez.


✏️ Exercice 1.1 — Fonction de conversion

Écrivez une fonction celsius_vers_fahrenheit(c) qui convertit une température de Celsius en Fahrenheit, sachant que F = C × 9/5 + 32. Testez-la avec plusieurs valeurs.

def celsius_vers_fahrenheit(c):
    ...

# Tests
for c in [0, 25, 100, -40]:
    print(f"{c}°C = {celsius_vers_fahrenheit(c)}°F")
def celsius_vers_fahrenheit(c):
    return c * 9 / 5 + 32

for c in [0, 25, 100, -40]:
    print(f"{c}°C = {celsius_vers_fahrenheit(c)}°F")
0°C = 32.0°F
25°C = 77.0°F
100°C = 212.0°F
-40°C = -40.0°F

✏️ Exercice 1.2 — Fonction polyvalente est_majeur

Écrivez une fonction est_majeur(age, age_legal=18) qui renvoie True si age >= age_legal, False sinon. Le deuxième paramètre permet de tester d’autres âges légaux (21 aux États-Unis par exemple).

def est_majeur(age, age_legal=18):
    ...

# Tests
print(est_majeur(20))
print(est_majeur(17))
print(est_majeur(20, age_legal=21))    # États-Unis
def est_majeur(age, age_legal=18):
    return age >= age_legal

print(est_majeur(20))                 # True
print(est_majeur(17))                 # False
print(est_majeur(20, age_legal=21))   # False (aux USA)
print(est_majeur(20, 21))             # False (par position)
True
False
False
False
  • Paramètre avec défaut (age_legal=18).
  • Appel par position : est_majeur(20, 21).
  • Appel par nom (keyword argument) : est_majeur(20, age_legal=21) → plus lisible.

✏️ Exercice 1.3 — Fonction à retour multiple

Écrivez une fonction statistiques(notes) qui renvoie un tuple (min, max, moyenne) des notes.

def statistiques(notes):
    ...

mini, maxi, moy = statistiques([12, 15, 9, 17])
print(f"Min: {mini}, Max: {maxi}, Moyenne: {moy}")
def statistiques(notes):
    return min(notes), max(notes), sum(notes) / len(notes)

mini, maxi, moy = statistiques([12, 15, 9, 17])
print(f"Min: {mini}, Max: {maxi}, Moyenne: {moy}")
Min: 9, Max: 17, Moyenne: 13.25
def statistiques(notes):
    if not notes:
        return None, None, None
    return min(notes), max(notes), sum(notes) / len(notes)

print(statistiques([]))
print(statistiques([12, 15, 9, 17]))
(None, None, None)
(9, 17, 13.25)

✏️ Exercice 1.4 — Inventaire de mots

Écrivez une fonction compter_mots(texte) qui renvoie un dictionnaire avec le nombre d’occurrences de chaque mot (insensible à la casse et à la ponctuation simple).

Exemple : compter_mots("Le chat et le chien"){"le": 2, "chat": 1, "et": 1, "chien": 1}.

def compter_mots(texte):
    ...

print(compter_mots("Le chat et le chien"))
print(compter_mots("Python est un langage. Python est populaire."))
def compter_mots(texte):
    # Normaliser : minuscules et retirer la ponctuation simple
    for char in ".,;:!?":
        texte = texte.replace(char, "")
    mots = texte.lower().split()

    occurrences = {}
    for mot in mots:
        occurrences[mot] = occurrences.get(mot, 0) + 1
    return occurrences

print(compter_mots("Le chat et le chien"))
print(compter_mots("Python est un langage. Python est populaire."))
{'le': 2, 'chat': 1, 'et': 1, 'chien': 1}
{'python': 2, 'est': 2, 'un': 1, 'langage': 1, 'populaire': 1}

✏️ Exercice 1.5 — Compteur local vs global

Écrivez une fonction encadrer(mot, caractere="*") qui renvoie le mot entouré de caractères. Exemple : encadrer("TOSA")"*** TOSA ***" (3 étoiles de chaque côté).

def encadrer(mot, caractere="*"):
    ...

print(encadrer("TOSA"))
print(encadrer("Python", caractere="-"))
print(encadrer("Bonjour", "#"))
def encadrer(mot, caractere="*"):
    decor = caractere * 3
    return f"{decor} {mot} {decor}"

print(encadrer("TOSA"))
print(encadrer("Python", caractere="-"))
print(encadrer("Bonjour", "#"))
*** TOSA ***
--- Python ---
### Bonjour ###
def encadrer(mot, caractere="*", largeur=3):
    decor = caractere * largeur
    return f"{decor} {mot} {decor}"

print(encadrer("TOSA"))
print(encadrer("Python", largeur=5))
print(encadrer("Hello", "=", 10))
*** TOSA ***
***** Python *****
========== Hello ==========

À retenir

Points clés du chapitre
  1. def nom(param1, param2): définit une fonction. Les () et : sont obligatoires.
  2. def mémorise la fonction ; seul l’appel nom(...) l’exécute.
  3. return renvoie une valeur. Sans return, la fonction renvoie None.
  4. Ne confondez pas print (affiche) et return (renvoie). Piège TOSA classique.
  5. Les paramètres avec valeur par défaut viennent après ceux sans défaut.
  6. JAMAIS de mutable ([], {}, set()) comme valeur par défaut → None + test.
  7. Arguments nommés (f(param=valeur)) — plus lisibles pour les appels longs.
  8. Variables locales par défaut ; global x pour modifier une globale (rare en pratique).
  9. Retour multiple : return a, b, c → tuple implicite.

← Retour à la présentation du niveauChapitre suivant : Docstrings →