212 votes

Comment puis-je éviter les problèmes causés par les paramètres par défaut de Python (par exemple, les arguments par défaut mutables "se souviennent" d'anciennes données) ?

Il semble parfois naturel d'avoir un paramètre par défaut qui est une liste vide. Cependant, Python produit un comportement inattendu dans les situations suivantes .

Prenons l'exemple de cette fonction :

def my_func(working_list=[]):
    working_list.append("a")
    print(working_list)

La première fois qu'il est appelé, la valeur par défaut fonctionnera, mais les appels suivants mettront à jour la liste existante (avec un seul "a" chaque appel) et imprimer la version mise à jour.

Comment puis-je corriger la fonction de manière à ce que, si elle est appelée plusieurs fois sans argument explicite, une nouvelle liste vide soit utilisée à chaque fois ?

247voto

HenryR Points 3026
def my_func(working_list=None):
    if working_list is None: 
        working_list = []

    # alternative:
    # working_list = [] if working_list is None else working_list

    working_list.append("a")
    print(working_list)

Les documents disent qu'il faut utiliser None comme valeur par défaut et explicitement le tester dans le corps de la fonction.

58voto

Zhenhua Points 1659

D'autres réponses ont déjà fourni les solutions directes demandées, mais comme il s'agit d'un écueil très courant pour les nouveaux programmeurs Python, il vaut la peine d'ajouter l'explication de la raison pour laquelle Python se comporte de cette manière, qui est joliment résumée dans Le guide de l'auto-stoppeur à Python sous Arguments par défaut mutables :

Les arguments par défaut de Python sont évalués une fois lorsque la fonction est définie, et non à chaque fois que la fonction est appelée (comme c'est le cas, par exemple, en Ruby). Cela signifie que si vous utilisez un argument par défaut mutable et que vous le mutez, vous devez volonté et a muté cet objet pour tous les futurs appels à la fonction.

18voto

bendin Points 6651

Cela n'a pas d'importance dans ce cas, mais vous pouvez utiliser l'identité d'objet pour tester None :

if working_list is None: working_list = []

Vous pouvez également tirer parti de la définition de l'opérateur booléen or en python :

working_list = working_list or []

Cependant, ce comportement sera inattendu si l'appelant vous donne une liste vide (qui compte comme false) comme working_list et s'attend à ce que votre fonction modifie la liste qu'il lui a donnée.

14voto

Si l'objectif de la fonction est de modifier le paramètre transmis en tant que working_list Voir la réponse de HenryR (=Aucun, vérifier l'existence d'aucun à l'intérieur).

Mais si vous n'avez pas l'intention de faire muter l'argument, mais simplement de l'utiliser comme point de départ d'une liste, vous pouvez simplement le copier :

def myFunc(starting_list = []):
    starting_list = list(starting_list)
    starting_list.append("a")
    print starting_list

(ou dans ce cas simple print starting_list + ["a"] mais je suppose que ce n'était qu'un exemple de jouet)

En général, la mutation des arguments est un mauvais style en Python. Les seules fonctions qui sont censées muter un objet sont les méthodes de l'objet. Il est encore plus rare de muter un argument optionnel - un effet de bord qui ne se produit que dans certains appels est-il vraiment la meilleure interface ?

  • Si vous le faites à partir de l'habitude C des "arguments de sortie", c'est complètement inutile - vous pouvez toujours renvoyer plusieurs valeurs sous forme de tuple.

  • Si vous faites cela pour construire efficacement une longue liste de résultats sans construire de listes intermédiaires, envisagez de l'écrire en tant que générateur et d'utiliser la fonction result_list.extend(myFunc()) lorsque vous l'appelez. De cette manière, vos conventions d'appel restent très propres.

Un modèle où la mutation d'un arg optionnel est fréquemment fait est un arg "memo" caché dans les fonctions récursives :

def depth_first_walk_graph(graph, node, _visited=None):
    if _visited is None:
        _visited = set()  # create memo once in top-level call

    if node in _visited:
        return
    _visited.add(node)
    for neighbour in graph[node]:
        depth_first_walk_graph(graph, neighbour, _visited)

3voto

Mapad Points 3033

Je suis peut-être hors sujet, mais rappelez-vous que si vous voulez simplement passer un nombre variable d'arguments, la méthode pythonique est de passer un tuple *args ou un dictionnaire **kargs . Elles sont facultatives et sont plus efficaces que la syntaxe myFunc([1, 2, 3]) .

Si vous voulez passer un tuple :

def myFunc(arg1, *args):
  print args
  w = []
  w += args
  print w
>>>myFunc(1, 2, 3, 4, 5, 6, 7)
(2, 3, 4, 5, 6, 7)
[2, 3, 4, 5, 6, 7]

Si vous voulez passer un dictionnaire :

def myFunc(arg1, **kargs):
   print kargs
>>>myFunc(1, option1=2, option2=3)
{'option2' : 2, 'option1' : 3}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X