madamasterclass.com

📔 Types mutables et problèmes associés

Etude des types mutables en pyton et problèmes associés


Dans ce cours, nous allons explorer les types mutables en Python et les problèmes qu'ils peuvent engendrer. Nous verrons pourquoi la mutabilité, bien que pratique, peut conduire à des bugs subtils et comment les éviter grâce à de bonnes pratiques de programmation.

En Python, les types mutables (comme les listes, dictionnaires et ensembles) permettent de modifier leur contenu après création. Cette caractéristique, bien que puissante, peut entraîner des comportements inattendus si mal maîtrisée.

Mutable vs Immutable en Python

1. Rappel : Types mutables vs immutables
  • Mutables (modifiables) : list, dict, set
  • Immutables (non modifiables) : int, float, str, tuple, frozenset
  • ✅ Problème clé : Les objets mutables peuvent être modifiés à travers plusieurs références

2. Problème 1 : Modification inattendue
  • ✦ Une même liste peut être référencée à plusieurs endroits
   a = [1, 2, 3]
   b = a  # b référence la même liste que a
   b.append(4)
   print(a)  # [1, 2, 3, 4] → a a aussi été modifié !
   
3. Problème 2 : Arguments par défaut
  • ✦ Un piège classique avec les listes comme paramètres par défaut
   def ajouter_element(element, liste=[]):
       liste.append(element)
       return liste

   print(ajouter_element(1))  # [1]
   print(ajouter_element(2))  # [1, 2] → La liste persiste entre les appels !
   
4. Problème 3 : Modification pendant l'itération
  • ✦ Modifier une collection pendant qu'on itère dessus est dangereux
   nombres = [1, 2, 3, 4]
   for n in nombres:
       if n % 2 == 0:
           nombres.remove(n)  # Risque de sauter des éléments
   print(nombres)  # Résultat inattendu possible
   
5. Bonnes pratiques
Problème Solution
Copies accidentelles Utiliser copy() ou list() pour une vraie copie
Arguments par défaut Utiliser None comme valeur par défaut
Modification pendant itération Itérer sur une copie ou utiliser des compréhensions de liste
6. Solutions concrètes
  • ✅ Créer des copies indépendantes :
   original = [1, 2, 3]
   copie = original.copy()  # ou list(original)
   copie.append(4)
   print(original)  # [1, 2, 3] → inchangé
   
  • ✅ Arguments par défaut sûrs :
   def ajouter_element(element, liste=None):
       if liste is None:
           liste = []
       liste.append(element)
       return liste
   
  • ✅ Itération sûre :
   # Solution 1 : Itérer sur une copie
   for n in nombres.copy():
       if n % 2 == 0:
           nombres.remove(n)
           
   # Solution 2 : Compréhension de liste
   nombres = [n for n in nombres if n % 2 != 0]
   
7. Conclusion

La mutabilité en Python est une épée à double tranchant. Si elle offre une grande flexibilité, elle peut aussi introduire des bugs subtils. En comprenant ces pièges et en appliquant les bonnes pratiques présentées, vous pourrez tirer pleinement parti des types mutables tout en évitant leurs écueils.

Application des Concepts de Programmation en Python

Exercice 1: Gestion des commandes ★ ★ ☆ ☆ ☆

Objectif pédagogique : Comprendre la mutabilité des objets en Python.

Énoncé :
Un restaurant utilise une liste partagée pour enregistrer les commandes. Observez ce qui se passe.

def nouvelle_commande(commande):
    plats = ["eau", "pain"]
    commande.append(plats)
    return commande

cmd1 = []
cmd2 = nouvelle_commande(cmd1)
cmd2[0].append("vin")

print(cmd1)

Correction :
La fonction ajoute une liste (plats) à la liste commande. Mais comme plats est un type mutable, toute modification (comme append("vin")) affecte aussi l'objet référencé dans cmd1.
Résultat affiché :
[['eau', 'pain', 'vin']]
Bonne pratique : utiliser commande.append(plats.copy()) pour éviter cette modification partagée.


Exercice 2: Ajout de notes ★ ★ ★ ☆ ☆

Objectif pédagogique : Identifier les problèmes de mutabilité avec les valeurs par défaut.

Énoncé :
Une école veut ajouter des notes aux élèves. La fonction suivante semble mal se comporter. Pourquoi ?

def ajouter_notes(nom, notes=[]):
    notes.append(10)
    print(f"{nom} : {notes}")

ajouter_notes("Alice")
ajouter_notes("Bob")

Correction :
La liste notes est définie par défaut comme une liste vide. Mais en Python, cette valeur par défaut est partagée entre tous les appels !
Résultat affiché :
Alice : [10]
Bob : [10, 10]
Bonne pratique :
def ajouter_notes(nom, notes=None):
    if notes is None:
        notes = []
    notes.append(10)
    print(f"{nom} : {notes}")


Exercice 3: Gestion de l'inventaire ★ ★ ★ ★ ☆

Objectif pédagogique : Comprendre la gestion des objets mutables dans des structures de données.

Énoncé :
Un jeu ajoute des objets à l’inventaire du joueur. Mais l’inventaire semble se remplir tout seul !

def creer_joueur(nom, inventaire=[]):
    inventaire.append("épée")
    return { "nom": nom, "inventaire": inventaire }

j1 = creer_joueur("Arthur")
j2 = creer_joueur("Lancelot")

print(j1)
print(j2)

Correction :
Encore une fois, la liste inventaire est partagée entre les deux joueurs ! Chaque appel modifie la même liste.
Résultat affiché :
{'nom': 'Arthur', 'inventaire': ['épée', 'épée']}
{'nom': 'Lancelot', 'inventaire': ['épée', 'épée']}
Bonne pratique : utiliser None comme valeur par défaut :
def creer_joueur(nom, inventaire=None):
    if inventaire is None:
        inventaire = []
    ...


Exercice 4: Suppression des nombres pairs ★ ★ ★ ★ ★

Objectif pédagogique : Identifier les erreurs de modification d'une liste en cours d'itération.

Énoncé :
Une fonction supprime les nombres pairs d'une liste. Le résultat est-il correct ?

nombres = [2, 3, 4, 5, 6]
for n in nombres:
    if n % 2 == 0:
        nombres.remove(n)
print(nombres)

Correction :
Le fait de modifier une liste pendant qu’on l’itère peut sauter des éléments car les indices changent à chaque suppression.
Résultat affiché :
[3, 5]
Bonne pratique : itérer sur une copie :
for n in nombres[:]:  # ou nombres.copy()
    if n % 2 == 0:
        nombres.remove(n)


Exercice 5: Duplication des listes ★ ★ ★ ★ ★

Objectif pédagogique : Comprendre la différence entre référence et copie d'une liste.

Énoncé :
Une boutique duplique ses listes de produits mais constate des modifications inattendues.

produits = ["chaussures", "t-shirt"]
copie = produits
copie.append("chapeau")
print(produits)

Correction :
La variable copie ne crée pas une copie indépendante mais une nouvelle référence vers la même liste.
Résultat affiché :
['chaussures', 't-shirt', 'chapeau']
Bonne pratique : utiliser copie = produits.copy() ou list(produits) pour une vraie duplication.


Application des Concepts de Programmation en Python

Exercice 6: Gestion des employés ★ ★ ☆ ☆ ☆

Objectif pédagogique : Comprendre l'utilisation des dictionnaires et la gestion des références en Python.

Énoncé :
Un service de gestion des employés utilise un dictionnaire pour stocker les informations des employés. Observez le comportement de votre code. Répondez aux questions suivantes : 1. Que se passe-t-il si vous modifiez les informations d'un employé après l'avoir ajouté à la liste ? 2. Comment pourriez-vous éviter de modifier les données d'un employé par inadvertance ?

def ajouter_employe(employes, nom, age):
    employes[nom] = {"age": age, "projet": None}

employes = {}
ajouter_employe(employes, "Alice", 30)
ajouter_employe(employes, "Bob", 25)

# Modification
employes["Alice"]["age"] = 31
print(employes)

1. Lorsque vous modifiez l'âge d'Alice, cela change directement la valeur dans le dictionnaire. En effet, les dictionnaires sont des types mutables, ce qui signifie que toute modification affecte la structure d'origine.
2. Pour éviter cela, vous pouvez faire une copie des informations de l'employé lors de l'ajout. Par exemple, en utilisant employes[nom] = {"age": age, "projet": None}.copy().
Résultat affiché :
{'Alice': {'age': 31, 'projet': None}, 'Bob': {'age': 25, 'projet': None}}
Cela montre que les données d'Alice ont été modifiées correctement.


Exercice 7: Traitement des notes des étudiants ★ ★ ★ ☆ ☆

Objectif pédagogique : Comprendre l'impact des listes mutables sur les valeurs par défaut des fonctions.

Énoncé :
Un professeur utilise une fonction pour ajouter des notes à ses étudiants. Cependant, il constate des résultats étranges. Répondez aux questions ci-dessous : 1. Pourquoi les notes de chaque étudiant semblent-elles s'accumuler ? 2. Comment corriger cette fonction pour qu'elle fonctionne comme prévu ?

def ajouter_note(nom, notes=[]):
    notes.append(10)
    print(f"{nom} : {notes}")

ajouter_note("Claire")
ajouter_note("David")

1. Les listes par défaut sont partagées entre les appels de la fonction. Cela signifie que chaque fois que vous ajoutez une note, elle est ajoutée à la même liste pour tous les étudiants.
2. Pour résoudre ce problème, vous pouvez utiliser None comme valeur par défaut et initialiser une nouvelle liste dans la fonction. Voici comment faire :
def ajouter_note(nom, notes=None):
    if notes is None:
        notes = []
    notes.append(10)
    print(f"{nom} : {notes}")
Cela garantit que chaque étudiant a sa propre liste de notes.


Exercice 8: Gestion des objets dans un jeu ★ ★ ★ ★ ☆

Objectif pédagogique : Identifier les problèmes de référence lors de la création d'objets.

Énoncé :
Un jeu vidéo crée des personnages avec un inventaire partagé. Répondez aux questions suivantes : 1. Que se passe-t-il lorsque vous créez plusieurs personnages ? 2. Comment éviter que l'inventaire soit partagé entre les personnages ?

def creer_personnage(nom, inventaire=[]):
    inventaire.append("épée")
    return {"nom": nom, "inventaire": inventaire}

p1 = creer_personnage("Héros")
p2 = creer_personnage("Mage")

print(p1)
print(p2)

1. Chaque personnage partage la même liste d'inventaire, ce qui signifie que toute modification de l'inventaire d'un personnage affecte l'autre. Cela se produit parce que la liste est un objet mutable.
2. Pour corriger cela, vous pouvez utiliser None comme valeur par défaut pour l'inventaire et créer une nouvelle liste pour chaque personnage :
def creer_personnage(nom, inventaire=None):
    if inventaire is None:
        inventaire = []
    inventaire.append("épée")
    return {"nom": nom, "inventaire": inventaire}
Chaque personnage aura ainsi son propre inventaire.


Exercice 9: Suppression des éléments d'une liste ★ ★ ★ ★ ★

Objectif pédagogique : Identifier les erreurs lors de la modification d'une liste pendant l'itération.

Énoncé :
Une fonction tente de supprimer les nombres pairs d'une liste, mais le résultat n'est pas celui attendu. Répondez aux questions suivantes : 1. Que se passe-t-il lorsque vous modifiez la liste pendant l'itération ? 2. Quelle serait la meilleure méthode pour supprimer des éléments d'une liste sans rencontrer d'erreurs ?

nombres = [1, 2, 3, 4, 5, 6]
for n in nombres:
    if n % 2 == 0:
        nombres.remove(n)
print(nombres)

1. Lors de la suppression d'éléments pendant l'itération, vous risquez de sauter certains éléments car les indices changent. Par exemple, si vous supprimez un élément, les éléments suivants se déplacent vers la gauche, mais l'itération continue avec l'indice suivant.
2. Pour éviter cela, vous pouvez itérer sur une copie de la liste ou utiliser une compréhension de liste :
nombres = [1, 2, 3, 4, 5, 6]
nombres = [n for n in nombres if n % 2 != 0]
print(nombres)
Cela garantit que vous ne modifiez pas la liste pendant l'itération.


Exercice 10: Duplication de listes ★ ★ ★ ★ ★

Objectif pédagogique : Comprendre les différences entre les références et les copies profondes d'une liste.

Énoncé :
Une boutique essaie de dupliquer ses listes de produits, mais elle rencontre des modifications inattendues. Répondez aux questions suivantes : 1. Pourquoi les modifications dans la liste copiée apparaissent-elles également dans la liste d'origine ? 2. Quelle méthode pouvez-vous utiliser pour créer une copie indépendante d'une liste ?

produits = ["chaussures", "t-shirt"]
copie = produits
copie.append("chapeau")
print(produits)

1. La variable copie ne crée pas une nouvelle liste, mais une référence vers la même liste. Cela signifie que toute modification de copie affecte également produits.
2. Pour créer une copie indépendante, vous pouvez utiliser produits.copy() ou list(produits) :
copie = produits.copy()
copie.append("chapeau")
print(produits)
Cela garantit que les modifications apportées à copie n'affectent pas produits.


Application des Concepts de Programmation en Python

Exercice 11: Gestion des commandes de produits ★ ★ ☆ ☆ ☆

Objectif pédagogique : Comprendre comment gérer des listes et éviter les erreurs de référence.

Énoncé :
Un magasin en ligne utilise une liste pour enregistrer les commandes de ses clients. Lisez le code suivant et répondez aux questions : 1. Que se passe-t-il si plusieurs clients passent des commandes en même temps ? 2. Comment cela pourrait-il affecter l'intégrité des données ? 3. Proposez une solution pour gérer chaque commande indépendamment.

def passer_commande(commande, articles):
    commande.append(articles)
    return commande

commande_client1 = []
commande_client2 = passer_commande(commande_client1, ["chaussures", "t-shirt"])
commande_client2[0].append("chapeau")

print(commande_client1)

1. Lorsque vous ajoutez des articles à commande_client2, cela modifie également commande_client1, car les deux variables pointent vers la même liste. C'est un problème de mutabilité des listes.
2. Cela peut entraîner une corruption des données, car les articles d'une commande peuvent être accidentellement ajoutés à une autre commande.
3. Pour résoudre ce problème, vous pouvez faire une copie de la liste des articles avant de l'ajouter à la commande :
def passer_commande(commande, articles):
    commande.append(articles.copy())
    return commande
Ainsi, chaque commande reste indépendante.


Exercice 12: Calcul des statistiques de notes ★ ★ ★ ☆ ☆

Objectif pédagogique : Comprendre la gestion des listes et le calcul des moyennes.

Énoncé :
Un enseignant souhaite calculer la moyenne des notes des étudiants. Examinez le code ci-dessous et répondez aux questions : 1. Pourquoi la moyenne calculée est-elle incorrecte si plusieurs appels sont effectués ? 2. Comment corriger le code pour qu'il fonctionne correctement à chaque appel ?

def ajouter_note(nom, notes=[]):
    notes.append(10)
    moyenne = sum(notes) / len(notes)
    print(f"{nom} : moyenne = {moyenne}")

ajouter_note("Alice")
ajouter_note("Bob")

1. La liste notes est partagée entre tous les appels. Ainsi, à chaque nouvel appel, la note de 10 est ajoutée à la même liste, ce qui fausse les calculs de moyenne.
2. Pour corriger cela, utilisez None comme valeur par défaut et initialisez une nouvelle liste pour chaque étudiant :
def ajouter_note(nom, notes=None):
    if notes is None:
        notes = []
    notes.append(10)
    moyenne = sum(notes) / len(notes)
    print(f"{nom} : moyenne = {moyenne}")
Cela permet de calculer des moyennes indépendantes pour chaque étudiant.


Exercice 13: Gestion des objets d'un jeu vidéo ★ ★ ★ ★ ☆

Objectif pédagogique : Identifier les problèmes de référence dans la gestion des objets.

Énoncé :
Un jeu vidéo permet de créer des personnages avec un inventaire. Examinez le code ci-dessous et répondez aux questions : 1. Que se passe-t-il lorsque plusieurs personnages sont créés ? 2. Comment cela affecte-t-il l'inventaire des personnages ? 3. Proposez une solution pour que chaque personnage ait un inventaire indépendant.

def creer_personnage(nom, inventaire=[]):
    inventaire.append("épée")
    return {"nom": nom, "inventaire": inventaire}

p1 = creer_personnage("Guerrier")
p2 = creer_personnage("Mage")

print(p1)
print(p2)

1. Les personnages partagent le même inventaire, ce qui signifie que toute modification de l'inventaire d'un personnage affecte également l'autre.
2. Cela se produit parce que la liste est un objet mutable. Par exemple, si vous ajoutez un objet à l'inventaire d'un personnage, il apparaîtra également dans l'inventaire de l'autre.
3. Pour corriger cela, vous devez utiliser None comme valeur par défaut et créer une nouvelle liste pour chaque personnage :
def creer_personnage(nom, inventaire=None):
    if inventaire is None:
        inventaire = []
    inventaire.append("épée")
    return {"nom": nom, "inventaire": inventaire}
Chaque personnage aura ainsi son propre inventaire.


Exercice 14: Filtrage des éléments d'une liste ★ ★ ★ ★ ★

Objectif pédagogique : Apprendre à gérer les erreurs lors de la suppression d'éléments d'une liste pendant l'itération.

Énoncé :
Une fonction tente de supprimer les nombres impairs d'une liste, mais le comportement n'est pas celui attendu. Répondez aux questions suivantes : 1. Pourquoi certaines valeurs sont-elles omises lors de l'itération ? 2. Quelle méthode pourriez-vous utiliser pour éviter ce problème ?

nombres = [1, 2, 3, 4, 5, 6]
for n in nombres:
    if n % 2 != 0:
        nombres.remove(n)
print(nombres)

1. En modifiant la liste pendant l'itération, vous pouvez "sauter" des éléments. Par exemple, lorsque vous supprimez un élément, les indices des éléments suivants changent, ce qui peut entraîner des omissions.
2. Pour éviter cela, vous pouvez créer une nouvelle liste avec les éléments que vous souhaitez conserver :
nombres = [1, 2, 3, 4, 5, 6]
nombres = [n for n in nombres if n % 2 == 0]
print(nombres)
Cela garantit que vous ne modifiez pas la liste pendant l'itération.


Exercice 15: Duplication de listes de produits ★ ★ ★ ★ ★

Objectif pédagogique : Comprendre les différences entre les références et les copies profondes d'une liste.

Énoncé :
Un magasin souhaite dupliquer sa liste de produits, mais il rencontre des modifications inattendues. Répondez aux questions suivantes : 1. Pourquoi les modifications dans la liste copiée apparaissent-elles également dans la liste d'origine ? 2. Quelle méthode pouvez-vous utiliser pour créer une copie indépendante d'une liste ?

produits = ["chaussures", "t-shirt"]
copie = produits
copie.append("chapeau")
print(produits)

1. La variable copie ne crée pas une nouvelle liste, mais une référence vers la même liste. Par conséquent, toute modification de copie affecte aussi produits.
2. Pour créer une copie indépendante, vous pouvez utiliser produits.copy() ou list(produits) :
copie = produits.copy()
copie.append("chapeau")
print(produits)
Cela garantit que les modifications apportées à copie n'affectent pas produits.


Forum(s) associé(s)

L'Art de la Philosophie : Interprétations et Débats

08 Apr, 2016

Explorez comment l'art et la philosophie s'entrelacent pour questionner notre perception de la réalité et de l'esthétique.

Read more.

Voyage à Travers la Liberté : Concepts et Dilemmes

27 Jan, 2014

Plongez dans les débats philosophiques sur la liberté, ses implications éthiques et les défis contemporains qui l'entourent.

Read more.

La Quête de la Vérité : Philosophies et Perspectives

30 Feb, 2015

Découvrez les différentes approches philosophiques de la vérité et comment elles influencent notre compréhension du monde.

Read more.
Page: