1. Effet de bord

Une fonction produit un effet de bord lorsqu'elle modifie quelque chose en dehors de sa propre valeur de retour : une variable globale, un objet mutable passé en argument, un fichier, l'affichage à l'écran…

score = 0

def marquer_point():
    global score
    score = score + 1   # effet de bord : modifie une variable globale
    return score
À retenir
print(), .append(), .sort(), .remove(), une affectation avec global sont des signaux typiques d'effet de bord.

2. Fonction pure & transparence référentielle

Une fonction est pure si elle ne produit aucun effet de bord, et si son résultat ne dépend que de ses arguments. Une fonction pure est référentiellement transparente : on peut remplacer l'appel par sa valeur de retour n'importe où, sans changer le comportement du programme.

def carre(x):        # pure
    return x * x

def carre_bavard(x): # impure : effet de bord (affichage)
    print("calcul en cours...")
    return x * x

3. Exercice 1 — Test express

Pure ou impure ?
# Fonction A
def double(x):
    return 2 * x

# Fonction B
compteur = 0
def incrementer():
    global compteur
    compteur += 1
    return compteur

# Fonction C
def ajoute_a_liste(liste, x):
    liste.append(x)
    return liste

# Fonction D
def moyenne(a, b):
    return (a + b) / 2

Classe chacune des 4 fonctions (pure / impure) en justifiant.

Voir la réponse
A : pure — aucun effet de bord.
B : impure — modifie une variable globale, le résultat dépend de l'état.
C : impure — mute la liste passée en argument.
D : pure — résultat entièrement déterminé par a et b.

4. Exercice 2 — Le détective (fonction passée en argument)

Écris une fonction générique capable de retrouver le premier élément d'une liste qui vérifie une condition quelconque, en passant la condition elle-même en argument.

def trouve_le_heros(liste, condition):
    """Renvoie le premier élément de liste qui vérifie condition,
    ou None si aucun ne convient."""
    ...

def est_majeur(age):
    return age >= 18

ages = [12, 15, 17, 19, 22, 8]
print(trouve_le_heros(ages, est_majeur))   # doit afficher 19

a) Complète avec une boucle. b) Teste avec une autre fonction condition. c) Teste avec une lambda directement en argument.

Voir la réponse
def trouve_le_heros(liste, condition):
    for element in liste:
        if condition(element):
            return element
    return None

print(trouve_le_heros(ages, lambda a: a > 20))  # 22
Une lambda est une fonction anonyme, écrite en une seule expression, pratique quand la condition n'est utilisée qu'une fois.

5. Exercice 3 — De trouve_le_heros à filter()

Python fournit filter(fonction, iterable) qui fait la même chose que trouve_le_heros, mais renvoie tous les éléments valides.

resultat = filter(est_majeur, ages)
print(resultat)          # <filter object at 0x...>
print(list(resultat))    # liste des éléments

a) Pourquoi print(resultat) n'affiche-t-il pas une liste ? b) Utilise filter() + next() pour ne récupérer que le premier majeur.

Voir la réponse
filter() est un itérateur paresseux (lazy) : il ne calcule les éléments qu'au moment où on les parcourt.
premier_majeur = next(filter(est_majeur, ages))
print(premier_majeur)   # 19
filter(condition, liste) généralise et optimise l'idée de trouve_le_heros.

6. Exercice 4 — La boîte à magie (vers map())

ingredients = ["aile de chauve-souris", "oeil de triton", "poil de dragon"]

Transforme chaque ingrédient en majuscules avec : a) une boucle for, b) une compréhension de liste, c) map().

Voir la réponse
# a) boucle
resultat = []
for ing in ingredients:
    resultat.append(ing.upper())

# b) compréhension
resultat = [ing.upper() for ing in ingredients]

# c) map()
resultat = list(map(str.upper, ingredients))
map() sépare clairement la fonction de transformation de la structure parcourue, sans variable intermédiaire.

Exercices défis (bonus)

Défi 1 défi

Le piège de la valeur par défaut mutable : que va afficher ce code ?

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

print(ajoute_a_panier("pomme"))
print(ajoute_a_panier("poire"))
Voir la réponse
Affiche ['pomme'] puis ['pomme', 'poire']. La liste par défaut n'est créée qu'une fois, à la définition, et est partagée entre tous les appels. Il faut préférer panier=None puis créer la liste à l'intérieur.
Défi 2 défi

Écris filtre_et_transforme(liste, condition, transformation) qui combine filter et map.

Voir la réponse
def filtre_et_transforme(liste, condition, transformation):
    return [transformation(x) for x in liste if condition(x)]
Défi 3 défi

Une fonction qui appelle random.random() est-elle pure ?

Voir la réponse
Non : deux appels peuvent renvoyer des résultats différents, elle n'est pas référentiellement transparente.