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.
 
To check your solution you need to sign in
Sign in to continue