Vous êtes ici : Accueil Actualités Bizarreries de la signature d'une fonction

Bizarreries de la signature d'une fonction

L'utilisation de types mutables dans la signature d'une fonction peut donner lieux à des bugs qui peuvent dans un premier temps être déroutants. Présentation du problème et de la solution.

Il est très souvent utile de préciser une valeur par défaut à un paramètre d'une fonction. Seulement, ce faisant, on peut avoir quelques soucis. Regardez plutôt l'exemple suivant :

>>> def example(immutable=5, mutable={'amount': 0}):
...     print immutable
...     print mutable
...     immutable += 10
...     mutable['amount'] += 10
...
>>> example()
5
{'amount': 0}
>>> example()
5
{'amount': 10}
>>> example()
5
{'amount': 20}

On voit bien que le type non mutable se comporte comme attendu alors que la valeur par défaut du type mutable est modifiée à chaque appel!

Par conséquent, Il ne faut pas utiliser des listes ou des dictionnaires en tant que valeur par défaut d'un paramètre d'une fonction.

Reste à expliquer ce mystère. En Python, une variable est juste un pointeur vers une zone mémoire qui contient réellement le contenu de la variable. Les types non mutable voient cette zone mémoire inchangée tout au long de la vie du programme Python. Ainsi, lorsque je modifie ma variable non mutable par cette opération immutable += 10, je calcule la valeur finale et je modifie le pointeur vers une nouvelle zone mémoire qui contient le résultat de l'opération. Ainsi, la zone mémoire initiale reste inchangée. Par contre, avec un type mutable, je modifie réellement la zone mémoire pointée, ce qui est le cas lorsque je fais cette opération mutable['amount'] += 10

Ce qu'il faut comprendre est que la fonction est un objet comme un autre. Elle est déclarée et mise en mémoire au moment où elle est lue et c'est à ce moment là que les valeurs par défauts sont crées et mises en mémoire, et non pas réinitialisées à chaque appel, d'où ce que l'on a pu observer.

La solution est de n'utiliser que des types mutables. Lorsque l'on a besoin d'un type non mutable, il faut le mettre par défaut à None et vérifier dans un second temps si une valeur a été passée ou non 

def example(immutable=5, mutable=None):
    if mutable is None:
        mutable = {'amount': 0}
    mutable['amount'] += 10

Cette brève est issue d'un article paru le 18 avril 2012 sur planet.python.org et a été librement adaptée.

Spinner