Arguments Mutables par Défaut
Lorsque vous combinez des arguments mutables avec des arguments par défaut en Python, cela peut mener à des résultats inattendus.
Illustrons cela avec un exemple simple. Supposons que vous êtes enseignant et que vous voulez créer une fonction pour enregistrer les notes des élèves pour différents devoirs. Vous souhaitez utiliser une liste pour suivre ces notes. Vous pourriez donc initialement écrire quelque chose comme ceci :
def record_score(new_score, score_list=[]):
score_list.append(new_score)
return score_list
print(record_score(95)) # [95]
print(record_score(85)) # [95, 85]
Cela semble fonctionner correctement. La fonction
record_score
prend en entrée une note et une liste. Si aucune liste n'est fournie, elle utilise une liste vide (le score_list=[]
dans la définition de la fonction).Lorsque vous appelez
record_score(95)
, il semble que la fonction fonctionne comme prévu — elle renvoie une liste avec la note 95. Mais quand vous appelez ensuite record_score(85)
, la fonction renvoie une liste contenant à la fois 95 et 85. C'est comme si la fonction se souvenait de la liste de la dernière fois qu'elle a été appelée.Cela peut vous surprendre. Dans la plupart des langages de programmation, lorsqu'une fonction se termine, toutes ses variables locales (comme
score_list
) sont supprimées et oubliées. La prochaine fois que la fonction est appelée, elle repart de zéro.Mais en Python, le comportement est différent lorsqu'un objet mutable est utilisé comme argument par défaut. Python ne crée l'argument par défaut qu'une seule fois, lors de la définition de la fonction. Donc, chaque fois que vous appelez
record_score
sans fournir de liste, Python utilise la même liste qu'il a créée la première fois. Si vous modifiez cette liste (en y ajoutant une note), la modification persiste lors des appels suivants de la fonction.Ainsi, dans notre fonction,
score_list
est partagé entre tous les appels de la fonction où nous ne fournissons pas notre propre liste. C'est pourquoi les notes s'accumulent dans notre exemple.Ce n'est pas ce que nous voulions ! Nous voulions que chaque appel à
record_score
commence avec une liste vide, sauf si nous fournissons notre propre liste.Pour éviter cela, une pratique courante est d'utiliser
None
comme valeur par défaut pour les arguments qui pourraient être mutables. Ensuite, à l'intérieur de la fonction, vous pouvez créer un nouvel objet mutable si l'argument est None
. Voici comment vous pourriez écrire la fonction record_score
pour éviter ce problème :def record_score(new_score, score_list=None):
if score_list is None: # Si aucune liste n'a été fournie,
score_list = [] # en créer une nouvelle
score_list.append(new_score)
return score_list
print(record_score(95)) # [95]
print(record_score(85)) # [85]
Dans cette version corrigée, chaque appel à
record_score
qui ne fournit pas sa propre liste reçoit une nouvelle liste. De cette façon, les notes ne sont pas partagées entre les appels de fonction.💡
Ainsi, la clé à retenir est la suivante :
Les arguments mutables par défaut en Python peuvent entraîner un comportement inattendu parce qu'ils sont créés une seule fois, et le même objet est utilisé à chaque appel de la fonction. Cela pourrait ne pas être ce que vous souhaitez, surtout si vous modifiez l'objet mutable. Pour éviter ce problème, vous pouvez utiliser
None
comme valeur par défaut et créer un nouvel objet dans la fonction si nécessaire.