151 votes

Quelles sont les bonnes utilisations des valeurs par défaut des arguments de fonctions mutables ?

C'est une erreur courante en Python de définir un objet mutable comme valeur par défaut d'un argument dans une fonction. Voici un exemple tiré de cet excellent article de David Goodger :

>>> def bad_append(new_item, a_list=[]):
        a_list.append(new_item)
        return a_list
>>> print bad_append('one')
['one']
>>> print bad_append('two')
['one', 'two']

L'explication de ce phénomène est la suivante ici .

J'en viens maintenant à ma question : Cette syntaxe peut-elle être utilisée à bon escient ?

Je veux dire que si tous ceux qui la rencontrent font la même erreur, la déboguent, comprennent le problème et, à partir de là, essaient de l'éviter, quelle est l'utilité d'une telle syntaxe ?

100voto

Duncan Points 25356

Vous pouvez l'utiliser pour mettre en cache des valeurs entre les appels de fonction :

def get_from_cache(name, cache={}):
    if name in cache: return cache[name]
    cache[name] = result = expensive_calculation()
    return result

mais en général, ce genre de choses se fait mieux avec une classe, car on peut alors avoir des attributs supplémentaires pour vider le cache, etc.

26voto

Peter Masiar Points 321

La réponse canonique est cette page : http://effbot.org/zone/default-values.htm

Il mentionne également trois "bons" cas d'utilisation de l'argument par défaut mutable :

  • lier une variable locale à valeur actuelle d'une variable externe dans un callback
  • cache/mémorisation
  • rebondissement local des noms globaux (pour un code hautement optimisé)

20voto

WolframH Points 2482

Il se peut que vous ne fassiez pas muter l'argument mutable, mais que vous attendiez un argument mutable :

def foo(x, y, config={}):
    my_config = {'debug': True, 'verbose': False}
    my_config.update(config)
    return bar(x, my_config) + baz(y, my_config)

(Oui, je sais que vous pouvez utiliser config=() dans ce cas particulier, mais je trouve cela moins clair et moins général.)

15voto

larsmans Points 167484
import random

def ten_random_numbers(rng=random):
    return [rng.random() for i in xrange(10)]

Utilise le random qui est en fait un singleton mutable, comme générateur de nombres aléatoires par défaut.

9voto

simon Points 619

Je sais qu'il s'agit d'un vieux sujet, mais juste pour le plaisir, j'aimerais ajouter un cas d'utilisation à ce fil. J'écris régulièrement des fonctions et des couches personnalisées pour TensorFlow/Keras, je télécharge mes scripts sur un serveur, j'y entraîne les modèles (avec des objets personnalisés), puis je sauvegarde les modèles et je les télécharge. Pour charger ces modèles, je dois alors fournir un dictionnaire contenant tous ces objets personnalisés.

Ce que vous pouvez faire dans des situations comme la mienne, c'est ajouter du code au module contenant ces objets personnalisés :

custom_objects = {}

def custom_object(obj, storage=custom_objects):
    storage[obj.__name__] = obj
    return obj

Ensuite, je peux décorer n'importe quelle classe/fonction qui doit figurer dans le dictionnaire.

@custom_object
def some_function(x):
    return 3*x*x + 2*x - 2

De plus, supposons que je veuille stocker mes fonctions de perte personnalisées dans un dictionnaire différent de celui de mes couches Keras personnalisées. L'utilisation de functools.partial me permet d'accéder facilement à un nouveau décorateur

import functools
import tf

custom_losses = {}
custom_loss = functools.partial(custom_object, storage=custom_losses)

@custom_loss
def my_loss(y, y_pred):
    return tf.reduce_mean(tf.square(y - y_pred))

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