Je sais que les arguments par défaut sont créés au moment de l'initialisation de la fonction et non à chaque appel de la fonction. Voir le code suivant:
def ook (item, lst=[]):
lst.append(item)
print 'ook', lst
def eek (item, lst=None):
if lst is None: lst = []
lst.append(item)
print 'eek', lst
max = 3
for x in xrange(max):
ook(x)
for x in xrange(max):
eek(x)
Ce que je ne comprends pas, c'est pourquoi cela a été mis en œuvre de cette façon. Quels avantages ce comportement offre-t-il par rapport à une initialisation à chaque appel?
Réponses:
Je pense que la raison en est la simplicité de mise en œuvre. Permettez-moi de développer.
La valeur par défaut de la fonction est une expression que vous devez évaluer. Dans votre cas, c'est une expression simple qui ne dépend pas de la fermeture, mais elle peut être quelque chose qui contient des variables libres -
def ook(item, lst = something.defaultList())
. Si vous devez concevoir Python, vous aurez le choix - l'évaluez-vous une fois lorsque la fonction est définie ou à chaque fois que la fonction est appelée. Python choisit la première (contrairement à Ruby, qui va avec la deuxième option).Il y a certains avantages à cela.
Tout d'abord, vous obtenez des gains de vitesse et de mémoire. Dans la plupart des cas, vous aurez des arguments par défaut immuables et Python peut les construire une seule fois, au lieu de chaque appel de fonction. Cela économise (une partie) de la mémoire et du temps. Bien sûr, cela ne fonctionne pas très bien avec des valeurs mutables, mais vous savez comment vous pouvez contourner.
Un autre avantage est la simplicité. Il est assez facile de comprendre comment l'expression est évaluée - elle utilise la portée lexicale lorsque la fonction est définie. S'ils allaient dans l'autre sens, la portée lexicale pourrait changer entre la définition et l'invocation et la rendre un peu plus difficile à déboguer. Python va un long chemin pour être extrêmement simple dans ces cas.
la source
Une façon de le dire est que le
lst.append(item)
ne mute pas lelst
paramètre.lst
fait toujours référence à la même liste. C'est juste que le contenu de cette liste a été modifié.Fondamentalement, Python n'a pas (que je me souvienne) de variables constantes ou immuables - mais il a des types constants et immuables. Vous ne pouvez pas modifier une valeur entière, vous pouvez seulement la remplacer. Mais vous pouvez modifier le contenu d'une liste sans la remplacer.
Comme un entier, vous ne pouvez pas modifier une référence, vous pouvez seulement la remplacer. Mais vous pouvez modifier le contenu de l'objet référencé.
Quant à la création de l'objet par défaut une fois, j'imagine que c'est principalement une optimisation, pour économiser sur les frais généraux de création d'objet et de récupération de place.
la source
Il vous permet de sélectionner le comportement que vous souhaitez, comme vous l'avez démontré dans votre exemple. Donc, si vous voulez que l'argument par défaut soit immuable, vous utilisez une valeur immuable , telle que
None
ou1
. Si vous souhaitez rendre l'argument par défaut mutable, vous utilisez quelque chose de mutable, tel que[]
. C'est juste de la flexibilité, certes, il peut mordre si vous ne le savez pas.la source
Je pense que la vraie réponse est: Python a été écrit comme un langage procédural et n'a adopté les aspects fonctionnels qu'après coup. Ce que vous recherchez, c'est que la valeur par défaut des paramètres soit effectuée comme une fermeture, et les fermetures en Python ne sont vraiment qu'à moitié cuites. Pour preuve de cet essai:
ce qui donne
[2, 2, 2]
où vous vous attendez à une véritable fermeture[0, 1, 2]
.Il y a beaucoup de choses que j'aimerais si Python avait la capacité d'envelopper les paramètres par défaut dans les fermetures. Par exemple:
Ce serait extrêmement pratique, mais "a" n'est pas dans la portée au moment de la définition de la fonction, donc vous ne pouvez pas le faire et à la place vous devez faire le maladroit:
Ce qui est presque la même chose ... presque.
la source
Un énorme avantage est la mémorisation. Ceci est un exemple standard:
et pour comparaison:
Mesures de temps en ipython:
la source
Cela se produit car la compilation en Python est effectuée en exécutant le code descriptif.
Si on disait
il serait assez clair que vous vouliez un nouveau tableau à chaque fois.
Mais si je dis:
Ici, je suppose que je veux créer des trucs dans diverses listes et avoir un seul fourre-tout global lorsque je ne spécifie pas de liste.
Mais comment le compilateur le devinerait-il? Alors pourquoi essayer? Nous pourrions compter sur le fait que cela ait été nommé ou non, et cela pourrait parfois aider, mais en réalité, ce serait juste deviner. Dans le même temps, il y a une bonne raison de ne pas essayer - la cohérence.
En l'état, Python exécute simplement le code. La variable list_of_all se voit déjà attribuer un objet, de sorte que cet objet est passé par référence dans le code par défaut x de la même manière qu'un appel à n'importe quelle fonction obtiendrait une référence à un objet local nommé ici.
Si nous voulions distinguer le cas sans nom du cas nommé, cela impliquerait que le code lors de la compilation exécute l'affectation d'une manière significativement différente de celle qui est exécutée au moment de l'exécution. Nous ne faisons donc pas le cas particulier.
la source
Cela se produit car les fonctions en Python sont des objets de première classe :
Il explique ensuite que la modification de la valeur du paramètre modifie la valeur par défaut pour les appels suivants, et qu'une solution simple consistant à utiliser None comme valeur par défaut, avec un test explicite dans le corps de la fonction, est tout ce qui est nécessaire pour éviter toute surprise.
Ce qui signifie que cela
def foo(l=[])
devient une instance de cette fonction lors de son appel et est réutilisé pour d'autres appels. Considérez les paramètres de fonction comme une séparation des attributs d'un objet.Les pro pourraient inclure de tirer parti de cela pour que les classes aient des variables statiques de type C. Il est donc préférable de déclarer les valeurs par défaut None et de les initialiser selon les besoins:
rendements:
au lieu de l'inattendu:
la source