Python Language Définition d'une fonction avec des arguments facultatifs mutables


Exemple

Il y a un problème lors de l'utilisation d' arguments facultatifs avec un type par défaut mutable (décrit dans Définition d'une fonction avec des arguments facultatifs ), ce qui peut potentiellement conduire à un comportement inattendu.

Explication

Ce problème se pose car les arguments par défaut d’une fonction sont initialisés une fois , au moment où la fonction est définie , et non (comme beaucoup d'autres langages) lorsque la fonction est appelée . Les valeurs par défaut sont stockées dans la variable membre __defaults__ l'objet fonction.

def f(a, b=42, c=[]):
    pass

print(f.__defaults__)
# Out: (42, [])

Pour les types immuables (voir Passage d'argument et mutabilité ), ce n'est pas un problème car il n'y a aucun moyen de modifier la variable. il ne peut jamais être réaffecté, laissant la valeur d'origine inchangée. Par conséquent, les valeurs suivantes sont garanties pour avoir la même valeur par défaut. Cependant, pour un type mutable , la valeur d'origine peut muter, en faisant des appels à ses différentes fonctions membres. Par conséquent, les appels successifs à la fonction ne sont pas garantis pour avoir la valeur par défaut initiale.

def append(elem, to=[]):
    to.append(elem)      # This call to append() mutates the default variable "to"
    return to

append(1)
# Out: [1]

append(2)  # Appends it to the internally stored list
# Out: [1, 2]

append(3, [])  # Using a new created list gives the expected result
# Out: [3]

# Calling it again without argument will append to the internally stored list again
append(4)   
# Out: [1, 2, 4]

Remarque: certains IDE comme PyCharm émettent un avertissement lorsqu'un type mutable est spécifié comme attribut par défaut.

Solution

Si vous voulez vous assurer que l'argument par défaut est toujours celui que vous spécifiez dans la définition de la fonction, la solution consiste à toujours utiliser un type immuable comme argument par défaut.

Un idiome commun pour atteindre cet objectif lorsqu'un type mutable est requis par défaut est d'utiliser None (immuable) comme argument par défaut, puis d'affecter la valeur par défaut réelle à la variable d'argument si elle est égale à None .

def append(elem, to=None):
    if to is None:
        to = []

    to.append(elem)
    return to