À quoi ressemble le problème ?
Imaginez que vous écrivez une fonction pour ajouter des tâches à une liste de tâches :
def add_task(task, todo_list=[]):
todo_list.append(task)
return todo_list
# Ajoutons la première tâche print (add_task ("Acheter du lait")) # ['Acheter du lait']ns une deuxième tâche print(add_task("Promener le chien")) # Nous attendons : ['Promener le chien'] # Nous obtenons : ['Acheter du lait', 'Promener le chien']⚠️ Que s'est-il passé ? Nous nous attendions à recevoir une nouvelle liste avec une tâche, mais la deuxième tâche a été ajoutée à la première ! Et nous n'avons enregistré la liste précédente nulle part. Magie ? Non, ce sont des arguments par défaut mutables.
Pourquoi cela se produit-il ?
Lorsque Python crée une fonction, il crée un objet par défaut une fois au moment de la définition de la fonction, et non à chaque appel. Cet objet est enregistré et réutilisé à chaque appel suivant.
Voyons ce qui se passe réellement :
def add_task(task, todo_list=[]):
todo_list.append(task)
print(f"List ID: {id(todo_list)}")
return todo_list
add_task("First task") # ID de la liste : 140234567891234add_task("Deuxième tâche") # ID de la liste : 140234567891234 (le même !) add_task("Troisième tâche") # ID de la liste : 140234567891234 (et encore !)💡 Point clé : Chaque fois, le même objet de la liste est utilisé. Tous les appels de fonction fonctionnent avec la même liste en mémoire, de sorte que toutes les modifications s'accumulent.
Quels types de données sont concernés ?
Ce problème concerne tous les types de données mutables en Python. Cela inclut les listes, les dictionnaires, les ensembles et les objets personnalisés. Mais les nombres, les chaînes et les tuples (types immuables) fonctionnent normalement, car ils ne peuvent pas être modifiés, vous ne pouvez en créer que de nouveaux.
# Code problématique avec dictionnaire def add_user(name, users_dict={}) :
users_dict[name] = True
return users_dict
print(add_user("Alexey")) # {'Alexey': True}print(add_user("Maria")) # {'Alexey': True, 'Maria': True} - les deux sont ici !# Code sécurisé avec la chaîne (immutable)def greet(name, greeting="Bonjour") : greeting = greeting + ", " + name
return greeting
print(greet("Ivan")) # Bonjour, Ivanprint(greet("Olga")) # Bonjour Olga - tout fonctionne comme prévuLa bonne décision.
La solution classique à ce problème consiste à utiliser None comme valeur par défaut, puis à créer un nouvel objet à l'intérieur de la fonction :
def add_task(task, todo_list=None):
if todo_list is None:
todo_list = []
todo_list.append(task)
return todo_list
# Maintenant, tout fonctionne correctementprint(add_task("Acheter du lait")) # ['Acheter du lait']print(add_task("Promener le chien")) # ['Promener le chien']✅ Règle : Chaque fois que nous ne transmettons pas la liste, une nouvelle liste vide est créée. Problème résolu !
Il en va de même pour les dictionnaires :
def create_profile(name, data=None):
if data is None:
data = {}
data['name'] = name
data['created_at'] = 'today'
return data
profile1 = create_profile("Alexey")
profile2 = create_profile("Maria")
print(profile1) # {'name': 'Alexey', 'created_at': 'aujourd'hui'}print(profile2) # {'name': 'Maria', 'created_at': 'aujourd'hui'}
Quand cela peut-il être utile ?
Il est intéressant de noter que parfois ce comportement peut être utilisé spécifiquement, par exemple, pour la mise en cache des résultats :
def get_config(config_cache={}):
if 'data' not in config_cache:
print("Loading configuration...")
config_cache['data'] = "important settings"
return config_cache['data']
print(get_config()) # Chargement de la configuration... paramètres importants print (get_config ()) # paramètres importants (ne sont plus chargés)💡 Remarque : Mais dans le code réel pour la mise en cache, il est préférable d'utiliser des outils spéciaux comme functools.lru_cache, afin que le code soit plus clair.
Comment éviter cette erreur ?
Rappelez-vous simplement la règle simple : n'utilisez jamais d'objets modifiables (listes, dictionnaires, ensembles) comme valeurs par défaut dans les arguments de fonction. Utilisez toujours None et créez l'objet souhaité à l'intérieur de la fonction.
Cette erreur est si courante que de nombreux linters (outils de vérification de code) vous avertissent spécifiquement à ce sujet. Si vous utilisez PyCharm, VSCode avec pylint ou flake8, vous verrez un avertissement lorsque vous essayez d'écrire un tel code.
Apprenez Python correctement dès la première fois !
Ce sujet et de nombreux autres sujets importants peuvent être étudiés en détail dans Codique - comprendre toutes les nuances avec des explications claires et consolider en pratique avec des tâches intéressantes. Chaque sujet est accompagné d'exemples de développement réel, et les exercices pratiques permettent d'appliquer immédiatement les connaissances acquises.
Et si vous avez des questions pendant la formation ou si vous souhaitez discuter de points complexes, nous avons déjà plus 2 000 personnes partageant les mêmes idées en mode actif canal Telegram, où ils vous aideront toujours à comprendre et à vous soutenir sur le chemin de la programmation !
