Toute personne qui bricole avec Python assez longtemps a été mordue (ou déchirée) par le problème suivant:
def foo(a=[]):
a.append(5)
return a
Les novices en Python s'attendent à ce que cette fonction retourne toujours une liste avec un seul élément: [5]
. Le résultat est plutôt très différent et très étonnant (pour un novice):
>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]
>>> foo()
[5, 5, 5, 5]
>>> foo()
Un de mes gestionnaires a eu sa première rencontre avec cette fonctionnalité et l’a appelée "un défaut de conception spectaculaire" de la langue. J'ai répondu que le comportement avait une explication sous-jacente, et que c'est en effet très déroutant et inattendu si vous ne comprenez pas les éléments internes. Cependant, je n'ai pas été en mesure de répondre à la question suivante: quelle est la raison pour laquelle l'argument par défaut est lié lors de la définition de la fonction et non lors de son exécution? Je doute que le comportement expérimenté ait une utilité pratique (qui a vraiment utilisé des variables statiques en C, sans reproducteurs?)
Edit:
Baczek a fait un exemple intéressant. Avec la plupart de vos commentaires et ceux d'Utaal en particulier, j'ai précisé:
>>> def a():
... print("a executed")
... return []
...
>>>
>>> def b(x=a()):
... x.append(5)
... print(x)
...
a executed
>>> b()
[5]
>>> b()
[5, 5]
Pour moi, il semble que la décision de conception était relative à l'endroit où placer la portée des paramètres: à l'intérieur de la fonction ou "ensemble" avec elle?
Faire la liaison à l'intérieur de la fonction signifierait que x
est effectivement lié à la valeur par défaut spécifiée lorsque la fonction est appelée, non définie, ce qui présenterait un défaut profond: la ligne def
serait "hybride". dans le sens où une partie de la liaison (de l'objet fonction) se produirait lors de la définition, et une partie (affectation des paramètres par défaut) au moment de l'appel de la fonction.
Le comportement réel est plus cohérent: tout le contenu de cette ligne est évalué lors de son exécution, c'est-à-dire lors de la définition de la fonction.
En réalité, ce n’est pas un défaut de conception, et ce n’est pas à cause de problèmes internes ou de performances.
Cela vient simplement du fait que les fonctions de Python sont des objets de première classe, et pas seulement un morceau de code.
Dès que vous réfléchissez de cette manière, cela prend tout son sens: une fonction est un objet évalué sur sa définition; Les paramètres par défaut sont en quelque sorte des "données membres" et leur état peut donc changer d'un appel à l'autre - exactement comme dans tout autre objet.
Dans tous les cas, Effbot explique très gentiment les raisons de ce comportement dans valeurs de paramètre par défaut en Python .
Je l’ai trouvé très clair et je suggère vraiment de le lire pour mieux comprendre le fonctionnement des objets fonctionnels.
Supposons que vous ayez le code suivant
fruits = ("apples", "bananas", "loganberries")
def eat(food=fruits):
...
Quand je vois la déclaration de eat, la chose la moins étonnante est de penser que si le premier paramètre n'est pas donné, il sera égal au Tuple ("apples", "bananas", "loganberries")
Cependant, supposé plus tard dans le code, je fais quelque chose comme
def some_random_function():
global fruits
fruits = ("blueberries", "mangos")
alors, si les paramètres par défaut étaient liés à l'exécution de la fonction plutôt qu'à la déclaration de la fonction, je serais étonné (très mal) de découvrir que les fruits avaient été modifiés. Ce serait plus surprenant pour l’OMI que de découvrir que votre fonction foo
ci-dessus mutait la liste.
Le vrai problème réside dans les variables mutables, et toutes les langues ont ce problème dans une certaine mesure. Voici une question: supposons que dans Java j'ai le code suivant:
StringBuffer s = new StringBuffer("Hello World!");
Map<StringBuffer,Integer> counts = new HashMap<StringBuffer,Integer>();
counts.put(s, 5);
s.append("!!!!");
System.out.println( counts.get(s) ); // does this work?
Ma carte utilise-t-elle la valeur de la touche StringBuffer
lorsqu'elle a été placée dans la carte ou la stocke-t-elle par référence? De toute façon, quelqu'un est étonné; soit la personne qui a essayé d'extraire l'objet de la Map
en utilisant une valeur identique à celle avec laquelle il l'a insérée, ou la personne qui ne semble pas pouvoir récupérer son objet même si la clé qu'elle utilise est littéralement le même objet que celui utilisé pour l'insérer dans la carte (c'est en fait la raison pour laquelle Python n'autorise pas l'utilisation de ses types de données intégrés mutables comme clés de dictionnaire).
Votre exemple est un bon exemple d'un cas où Python nouveaux arrivants seront surpris et mordus. Mais je dirais que si nous "corrigions" cela, cela ne ferait que créer une situation différente où ils seraient piqués à la place, et celle-ci serait encore moins intuitive. De plus, c'est toujours le cas lorsqu'il s'agit de variables mutables; vous rencontrez toujours des cas où quelqu'un pourrait intuitivement s'attendre à un comportement ou au comportement opposé en fonction du code écrit.
J'aime personnellement l'approche actuelle de Python: les arguments de fonction par défaut sont évalués lorsque la fonction est définie et cet objet est toujours l'objet par défaut. Je suppose qu'ils pourraient utiliser une liste vide dans des cas spéciaux, mais ce type de boîtier spécial causerait encore plus d'étonnement, pour ne pas dire être incompatible avec le passé.
AFAICS personne n'a encore posté la partie pertinente de la documentation :
Les valeurs de paramètre par défaut sont évaluées lors de l'exécution de la définition de la fonction. Cela signifie que l'expression est évaluée une fois, lorsque la fonction est définie et que " "pré-calculé" est utilisé pour chaque appel. Cela est particulièrement important à comprendre lorsqu'un paramètre par défaut est un objet modifiable, tel qu'une liste ou un dictionnaire: si la fonction modifie l'objet (par exemple en ajoutant un élément à une liste), la valeur par défaut est modifiée. Ce n'est généralement pas ce qui était prévu. Une solution consiste à utiliser None par défaut et à le tester explicitement dans le corps de la fonction [...]
Je ne connais rien au fonctionnement interne de l'interprète Python (et je ne suis pas non plus un expert en compilateurs et interprètes), alors ne me blâmez pas si je propose quelque chose d'insensible ou d'impossible.
À condition que python objets soient mutables Je pense que cela devrait être pris en compte lors de la conception des options d'arguments par défaut. Lorsque vous instanciez une liste:
a = []
vous vous attendez à obtenir une nouvelle liste référencée par a
.
Pourquoi le a=[]
dans
def x(a=[]):
instancier une nouvelle liste sur la définition de la fonction et non sur l'invocation? C'est comme si vous demandiez "si l'utilisateur ne fournit pas l'argument, alors instancie une nouvelle liste et l'utilise comme si elle avait été produite par le votre interlocuteur". Je pense que c'est ambigu à la place:
def x(a=datetime.datetime.now()):
utilisateur, voulez-vous que a
utilise par défaut la date et l'heure correspondant à la définition ou à l'exécution de x
? Dans ce cas, comme dans le précédent, je garderai le même comportement que si l'argument par défaut "assignation" était la première instruction de la fonction (datetime.now()
appelée à l'invocation de la fonction). D'autre part, si l'utilisateur souhaitait le mappage définition-temps, il pouvait écrire:
b = datetime.datetime.now()
def x(a=b):
Je sais, je sais: c'est une fermeture. Sinon, Python pourrait fournir un mot clé pour forcer la liaison définition-heure:
def x(static a=b):
Eh bien, la raison en est tout simplement que les liaisons sont effectuées lorsque le code est exécuté, et la définition de la fonction est exécutée, ainsi ... lorsque les fonctions sont définies.
Comparez ceci:
class BananaBunch:
bananas = []
def addBanana(self, banana):
self.bananas.append(banana)
Ce code souffre exactement du même incident imprévu. bananas est un attribut de classe et, par conséquent, lorsque vous y ajoutez des éléments, il est ajouté à toutes les instances de cette classe. La raison est exactement la même.
C'est juste "Comment ça marche", et le faire fonctionner différemment dans le cas de la fonction serait probablement compliqué, et dans le cas de la classe probablement impossible, ou du moins ralentir l'instanciation d'objet beaucoup, car vous auriez à garder le code de classe autour et l'exécuter lorsque des objets sont créés.
Oui, c'est inattendu. Mais une fois que le penny est tombé, cela correspond parfaitement au fonctionnement de Python en général. En fait, c'est un bon outil pédagogique, et une fois que vous comprenez pourquoi cela se produit, vous allez beaucoup mieux python.
Cela dit, il devrait figurer en bonne place dans tout bon tutoriel Python. Parce que, comme vous le dites, tout le monde se heurte tôt ou tard à ce problème.
Je suis vraiment surpris que personne n'ait effectué l'introspection perspicace offerte par Python (2
et 3
s'appliquent) sur les callables.
Soit une petite fonction simple func
définie comme:
>>> def func(a = []):
... a.append(5)
Lorsque Python le rencontre, la première chose à faire est de le compiler afin de créer un objet code
pour cette fonction. Python évalue * puis stocke les arguments par défaut (une liste vide []
ici) dans l'objet fonction lui-même . Comme le dit la réponse principale: la liste a
peut maintenant être considérée comme un membre de la fonction func
.
Faisons donc une introspection, un avant et un après pour examiner comment la liste est développée à l'intérieur de l'objet fonction. J'utilise Python 3.x
pour cela, pour Python 2, la même chose s'applique (utilisez __defaults__
ou func_defaults
dans Python 2; oui, deux noms pour la même chose).
>>> def func(a = []):
... a.append(5)
...
Après que Python ait exécuté cette définition, tous les paramètres par défaut spécifiés (a = []
here) et les cram dans l'attribut __defaults__
pour l'objet de fonction (pertinent section: Callables):
>>> func.__defaults__
([],)
O.k, donc une liste vide comme seule entrée dans __defaults__
, comme prévu.
Exécutons maintenant cette fonction:
>>> func()
Maintenant, revoyons ces __defaults__
:
>>> func.__defaults__
([5],)
Étonné? La valeur à l'intérieur de l'objet change! Les appels consécutifs à la fonction vont maintenant simplement s'ajouter à cet objet list
incorporé:
>>> func(); func(); func()
>>> func.__defaults__
([5, 5, 5, 5],)
Donc, voilà, la raison pour laquelle ce 'défaut' se produit, c'est parce que les arguments par défaut font partie de l'objet fonction. Il n'y a rien de bizarre ici, c'est juste un peu surprenant.
Pour résoudre ce problème, la solution courante consiste à utiliser None
par défaut, puis à l'initialiser dans le corps de la fonction:
def func(a = None):
# or: a = [] if a is None else a
if a is None:
a = []
Etant donné que le corps de la fonction est à chaque fois exécuté à nouveau, vous obtenez toujours une nouvelle liste vide si aucun argument n'a été passé pour a
.
Pour vérifier que la liste dans __defaults__
est identique à celle utilisée dans la fonction func
, vous pouvez simplement changer votre fonction pour renvoyer le id
de la liste a
utilisée à l'intérieur. le corps de fonction. Ensuite, comparez-le à la liste dans __defaults__
(position [0]
dans __defaults__
) et vous verrez comment ils se réfèrent effectivement à la même instance de liste:
>>> def func(a = []):
... a.append(5)
... return id(a)
>>>
>>> id(func.__defaults__[0]) == func()
True
Tous avec le pouvoir de l'introspection!
* Pour vérifier que Python évalue les arguments par défaut lors de la compilation de la fonction, essayez d'exécuter les opérations suivantes:
def bar(a=input('Did you just see me without calling the function?')):
pass # use raw_input in Py2
comme vous le remarquerez, input()
est appelé avant que le processus de construction de la fonction et de sa liaison au nom bar
ne soit créé.
J'avais l'habitude de penser que la création des objets au moment de l'exécution serait la meilleure approche. Je suis moins certain maintenant, car vous perdez certaines fonctionnalités utiles, bien que cela puisse en valoir la peine, simplement pour éviter toute confusion. Les inconvénients sont les suivants:
1. Performance
def foo(arg=something_expensive_to_compute())):
...
Si l’évaluation du temps d’appel est utilisée, la fonction coûteuse est appelée chaque fois que votre fonction est utilisée sans argument. Soit vous payez un prix élevé pour chaque appel, soit vous devez mettre en cache manuellement la valeur, en polluant votre espace de noms et en ajoutant de la verbosité.
2. Forcer les paramètres liés
Une astuce utile consiste à lier les paramètres d’un lambda à la liaison actuelle d’une variable lors de la création du lambda. Par exemple:
funcs = [ lambda i=i: i for i in range(10)]
Ceci retourne une liste de fonctions qui retournent 0,1,2,3 ... respectivement. Si le comportement est modifié, ils lieront plutôt i
à la valeur de la durée de l'appel de i, de sorte que vous obtiendrez une liste de fonctions. que tous retournent 9
.
Autrement, la seule façon de mettre en œuvre cette solution serait de créer une nouvelle fermeture avec le i lié, c'est-à-dire:
def make_func(i): return lambda: i
funcs = [make_func(i) for i in range(10)]
. Introspection
Considérons le code:
def foo(a='test', b=100, c=[]):
print a,b,c
Nous pouvons obtenir des informations sur les arguments et les valeurs par défaut en utilisant le module inspect
, qui
>>> inspect.getargspec(foo)
(['a', 'b', 'c'], None, None, ('test', 100, []))
Cette information est très utile pour la génération de documents, la métaprogrammation, les décorateurs, etc.
Supposons maintenant que le comportement des valeurs par défaut puisse être modifié de sorte que cela équivaut à:
_undefined = object() # sentinel value
def foo(a=_undefined, b=_undefined, c=_undefined)
if a is _undefined: a='test'
if b is _undefined: b=100
if c is _undefined: c=[]
Cependant, nous avons perdu la capacité d'introspection et de voir quels sont les arguments par défaut . Comme les objets n'ont pas été construits, nous ne pouvons jamais les obtenir sans appeler la fonction. Le mieux que nous puissions faire est de stocker le code source et de le renvoyer sous forme de chaîne.
Simplicité : le comportement est simple dans le sens suivant: la plupart des gens ne tombent dans ce piège qu'une seule fois, pas plusieurs fois.
Cohérence : Python always transmet des objets, pas des noms. Le paramètre par défaut fait évidemment partie de l'en-tête de la fonction (pas du corps de la fonction). Il doit donc être évalué au moment du chargement du module (et uniquement au moment du chargement du module, sauf si imbriqué), et non au moment de l'appel de la fonction.
Utilité : Comme l'indique Frederik Lundh dans son explication de "Valeurs de paramètre par défaut en Python" , le comportement actuel peut être assez utile pour la programmation avancée. (Utiliser avec parcimonie.)
Documentation suffisante : dans la documentation la plus élémentaire Python, le tutoriel, le problème est annoncé à haute voix en tant que "Avertissement important" dans la sous-section first de la section "Pour en savoir plus sur la définition de fonctions" . L'avertissement utilise même des caractères gras, rarement appliqués en dehors des titres. RTFM: Lisez le manuel détaillé.
Méta-apprentissage : Tomber dans le piège est en fait un moment très utile (du moins si vous êtes un apprenant réfléchi), car vous comprendrez mieux pointez "Cohérence" ci-dessus et cela vous en apprendra beaucoup sur Python.
Ce comportement est facile à expliquer par:
Alors:
def x(a=0, b=[], c=[], d=0):
a = a + 1
b = b + [1]
c.append(1)
print a, b, c
a
ne change pas - chaque appel d'affectation crée un nouvel objet int - un nouvel objet est impriméb
ne change pas - le nouveau tableau est construit à partir de la valeur par défaut et impriméc
changements - l'opération est effectuée sur le même objet - et il est impriméCe que vous demandez, c'est pourquoi ceci:
def func(a=[], b = 2):
pass
n'est pas équivalent en interne à ceci:
def func(a=None, b = None):
a_default = lambda: []
b_default = lambda: 2
def actual_func(a=None, b=None):
if a is None: a = a_default()
if b is None: b = b_default()
return actual_func
func = func()
sauf pour le cas d'appeler explicitement func (None, None), que nous ignorerons.
En d'autres termes, au lieu d'évaluer les paramètres par défaut, pourquoi ne pas stocker chacun d'entre eux et les évaluer lorsque la fonction est appelée?
Une réponse est probablement là: il transformerait efficacement chaque fonction avec des paramètres par défaut en une fermeture. Même si tout est caché dans l'interprète et non pas comme une fermeture complète, les données doivent être stockées quelque part. Ce serait plus lent et utiliser plus de mémoire.
1) Le soi-disant problème de "Mutable Default Argument" est en général un exemple particulier démontrant que:
"Toutes les fonctions avec ce problème souffrent également d'un problème similaire d'effet secondaire sur le paramètre actuel ,"
Cela va à l’encontre des règles de la programmation fonctionnelle, généralement non souhaitable et devrait être corrigé tous les deux.
Exemple:
def foo(a=[]): # the same problematic function
a.append(5)
return a
>>> somevar = [1, 2] # an example without a default parameter
>>> foo(somevar)
[1, 2, 5]
>>> somevar
[1, 2, 5] # usually expected [1, 2]
Solution : une copie copie
Une solution absolument sûre consiste à copy
ou deepcopy
l'objet d'entrée en premier et ensuite faire quoi que ce soit avec la copie.
def foo(a=[]):
a = a[:] # a copy
a.append(5)
return a # or everything safe by one line: "return a + [5]"
Beaucoup de types mutables intégrés ont une méthode de copie comme some_dict.copy()
ou some_set.copy()
ou peuvent être copiés facilement comme somelist[:]
ou list(some_list)
. Chaque objet peut également être copié par copy.copy(any_object)
ou plus en profondeur par copy.deepcopy()
(ce dernier est utile si l'objet mutable est composé à partir d'objets mutables). Certains objets sont fondamentalement basés sur des effets secondaires tels que l’objet "fichier" et ne peuvent pas être reproduits de manière significative par copie. copie
Exemple de problème pour ne _ question similaireSO
class Test(object): # the original problematic class
def __init__(self, var1=[]):
self._var1 = var1
somevar = [1, 2] # an example without a default parameter
t1 = Test(somevar)
t2 = Test(somevar)
t1._var1.append([1])
print somevar # [1, 2, [1]] but usually expected [1, 2]
print t2._var1 # [1, 2, [1]] but usually expected [1, 2]
Il ne devrait pas non plus être enregistré dans aucun attribut public d'une instance renvoyée par cette fonction. (En supposant que les attributs d'instance privés ne soient pas modifiés par convention en dehors de cette classe ou de ces sous-classes. I.e. _var1
est un attribut privé)
Conclusion:
Les objets de paramètres d'entrée ne doivent pas être modifiés à la place (mutés) ni liés à un objet renvoyé par la fonction. (Si nous préférons programmer sans effets secondaires, cela est vivement recommandé. Voir Wiki sur "effets secondaires" (Les deux premiers paragraphes sont pertinents dans ce contexte.).
2)
Seulement si l'effet secondaire sur le paramètre réel est requis mais non souhaité sur le paramètre par défaut, la solution utile est alors def ...(var1=None):
if var1 is None:
var1 = []
Plus ..
3) Dans certains cas, comportement utile des paramètres par défaut modifiable .
Cela n'a en fait rien à voir avec les valeurs par défaut, à part le fait qu'il se présente souvent comme un comportement inattendu lorsque vous écrivez des fonctions avec des valeurs par défaut modifiables.
>>> def foo(a):
a.append(5)
print a
>>> a = [5]
>>> foo(a)
[5, 5]
>>> foo(a)
[5, 5, 5]
>>> foo(a)
[5, 5, 5, 5]
>>> foo(a)
[5, 5, 5, 5, 5]
Aucune valeur par défaut en vue dans ce code, mais vous obtenez exactement le même problème.
Le problème est que foo
est modification une variable mutable transmise par l'appelant, lorsque l'appelant ne s'y attend pas. Un code comme celui-ci conviendrait si la fonction s'appelait quelque chose comme append_5
; alors l'appelant appellerait la fonction pour modifier la valeur transmise et le comportement serait attendu. Mais il est très peu probable qu'une telle fonction prenne un argument par défaut et ne renvoie probablement pas la liste (puisque l'appelant a déjà une référence à cette liste; celle qu'elle vient de transmettre).
Votre foo
originale, avec un argument par défaut, ne devrait pas modifier a
si elle a été explicitement transmise ou si elle a obtenu la valeur par défaut. Votre code doit laisser les arguments mutables seuls à moins qu'il ne soit clairement indiqué dans le contexte/nom/documentation que les arguments sont supposés être modifiés. Utiliser des valeurs mutables transmises en tant qu'arguments comme des temporaires locaux est une très mauvaise idée, que nous soyons dans Python ou non et qu'il y ait ou non des arguments par défaut.
Si vous devez manipuler de manière destructive un temporaire local en cours de calcul, et si vous devez commencer votre manipulation à partir d'une valeur d'argument, vous devez en faire une copie.
Sujet déjà occupé, mais d'après ce que j'ai lu ici, ce qui suit m'a aidé à comprendre comment cela fonctionne en interne:
def bar(a=[]):
print id(a)
a = a + [1]
print id(a)
return a
>>> bar()
4484370232
4484524224
[1]
>>> bar()
4484370232
4484524152
[1]
>>> bar()
4484370232 # Never change, this is 'class property' of the function
4484523720 # Always a new object
[1]
>>> id(bar.func_defaults[0])
4484370232
C'est une optimisation de la performance. En raison de cette fonctionnalité, lequel de ces deux appels de fonction pensez-vous le plus rapide?
def print_Tuple(some_Tuple=(1,2,3)):
print some_Tuple
print_Tuple() #1
print_Tuple((1,2,3)) #2
Je vais vous donner un indice. Voici le désassemblage (voir http://docs.python.org/library/dis.html ):
#
10 LOAD_GLOBAL 0 (print_Tuple)
3 CALL_FUNCTION 0
6 POP_TOP
7 LOAD_CONST 0 (None)
10 RETURN_VALUE
#
2 0 LOAD_GLOBAL 0 (print_Tuple)
3 LOAD_CONST 4 ((1, 2, 3))
6 CALL_FUNCTION 1
9 POP_TOP
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
Je doute que le comportement expérimenté ait une utilité pratique (qui a vraiment utilisé des variables statiques en C, sans reproducteurs?)
Comme vous pouvez le constater, présente un avantage en termes de performances lorsque des arguments par défaut immuables sont utilisés. Cela peut faire la différence s'il s'agit d'une fonction fréquemment appelée ou si l'argument par défaut est long à construire. De plus, gardez à l'esprit que Python n'est pas C. En C, vous avez des constantes qui sont à peu près gratuites. En Python, vous ne bénéficiez pas de cet avantage.
La réponse la plus courte serait probablement "définition est exécution", donc tout l'argument n'a pas de sens strict. Comme exemple plus artificiel, vous pouvez citer ceci:
def a(): return []
def b(x=a()):
print x
Espérons que cela suffira pour montrer que le fait de ne pas exécuter les expressions d'argument par défaut au moment de l'exécution de l'instruction def
n'est pas facile, n'a pas de sens, ou les deux.
Je conviens que c'est un piège lorsque vous essayez d'utiliser des constructeurs par défaut.
Les arguments par défaut sont évalués au moment où la fonction est compilée dans un objet fonction. Lorsqu'ils sont utilisés par la fonction, plusieurs fois par cette fonction, ils sont et restent le même objet.
Lorsqu'ils sont mutables, lorsqu'ils sont mutés (par exemple, en y ajoutant un élément), ils restent mutés lors d'appels consécutifs.
Ils restent mutés car ils sont le même objet à chaque fois.
Puisque la liste est liée à la fonction lorsque l'objet de la fonction est compilé et instancié, ceci:
def foo(mutable_default_argument=[]): # make a list the default argument
"""function that uses a list"""
est presque exactement équivalent à ceci:
_a_list = [] # create a list in the globals
def foo(mutable_default_argument=_a_list): # make it the default argument
"""function that uses a list"""
del _a_list # remove globals name binding
Voici une démonstration - vous pouvez vérifier qu’il s’agit du même objet chaque fois qu’ils sont référencés par
example.py
print('1. Global scope being evaluated')
def create_list():
'''noisily create a list for usage as a kwarg'''
l = []
print('3. list being created and returned, id: ' + str(id(l)))
return l
print('2. example_function about to be compiled to an object')
def example_function(default_kwarg1=create_list()):
print('appending "a" in default default_kwarg1')
default_kwarg1.append("a")
print('list with id: ' + str(id(default_kwarg1)) +
' - is now: ' + repr(default_kwarg1))
print('4. example_function compiled: ' + repr(example_function))
if __== '__main__':
print('5. calling example_function twice!:')
example_function()
example_function()
et l'exécuter avec python example.py
:
1. Global scope being evaluated
2. example_function about to be compiled to an object
3. list being created and returned, id: 140502758808032
4. example_function compiled: <function example_function at 0x7fc9590905f0>
5. calling example_function twice!:
appending "a" in default default_kwarg1
list with id: 140502758808032 - is now: ['a']
appending "a" in default default_kwarg1
list with id: 140502758808032 - is now: ['a', 'a']
Cet ordre d'exécution est souvent source de confusion pour les nouveaux utilisateurs de Python. Si vous comprenez le modèle d'exécution Python, il devient tout à fait attendu.
Mais c’est pourquoi l’instruction habituelle pour les nouveaux utilisateurs est de créer leurs arguments par défaut comme ceci:
def example_function_2(default_kwarg=None):
if default_kwarg is None:
default_kwarg = []
Cela utilise le singleton None en tant qu'objet sentinel pour indiquer à la fonction si nous avons obtenu un argument autre que le paramètre par défaut. Si nous n'obtenons aucun argument, nous voulons utiliser une nouvelle liste vide, []
, par défaut.
Comme le section du tutoriel sur le flux de contrôle dit:
Si vous ne souhaitez pas que la valeur par défaut soit partagée entre les appels suivants, vous pouvez écrire la fonction comme suit:
def f(a, L=None): if L is None: L = [] L.append(a) return L
Une solution de contournement simple utilisant Aucun
>>> def bar(b, data=None):
... data = data or []
... data.append(b)
... return data
...
>>> bar(3)
[3]
>>> bar(3)
[3]
>>> bar(3)
[3]
>>> bar(3, [34])
[34, 3]
>>> bar(3, [34])
[34, 3]
Ce comportement n’est pas surprenant si vous tenez compte des éléments suivants:
Le rôle de (2) a été couvert en détail dans ce fil de discussion. (1) est probablement le facteur causant l'étonnement, car ce comportement n'est pas "intuitif" lorsqu'il provient d'autres langues.
(1) est décrit dans le Python tutoriel sur les classes . Dans une tentative d'affectation d'une valeur à un attribut de classe en lecture seule:
... toutes les variables trouvées en dehors de la portée la plus interne sont en lecture seule (; une tentative d'écriture dans une telle variable créera simplement une nouvelle variable locale dans la partie la plus interne. scope, en laissant inchangée la variable externe portant le même nom ).
Retournez à l'exemple original et considérez les points ci-dessus:
def foo(a=[]):
a.append(5)
return a
Ici foo
est un objet et a
est un attribut de foo
(disponible sur foo.func_defs[0]
). Puisque a
est une liste, a
est modifiable et constitue donc un attribut en lecture-écriture de foo
. Il est initialisé à la liste vide spécifiée par la signature lorsque la fonction est instanciée et est disponible pour la lecture et l'écriture tant que l'objet de fonction existe.
L'appel de foo
sans remplacer une valeur par défaut utilise la valeur de cette valeur par défaut de foo.func_defs
. Dans ce cas, foo.func_defs[0]
est utilisé pour a
dans la portée du code de l'objet de fonction. Les modifications apportées à a
change foo.func_defs[0]
, qui fait partie de l'objet foo
et persiste entre l'exécution du code dans foo
.
Maintenant, comparons cela à l'exemple de la documentation sur émulant le comportement d'argument par défaut d'autres langages , de sorte que les valeurs par défaut de la signature de la fonction soient utilisées à chaque exécution de la fonction:
def foo(a, L=None):
if L is None:
L = []
L.append(a)
return L
En prenant en compte (1) et (2) , on peut voir pourquoi cela accomplit le comportement souhaité:
foo
est instancié, foo.func_defs[0]
est défini sur None
, un objet immuable.L
dans l'appel de fonction), foo.func_defs[0]
(None
) est disponible dans la portée locale en tant que L
.L = []
, l'affectation ne peut aboutir à foo.func_defs[0]
, car cet attribut est en lecture seule.L
est créé dans la portée locale et utilisé pour le reste de l'appel de fonction. foo.func_defs[0]
reste donc inchangé pour les invocations futures de foo
.Les solutions ici sont:
None
comme valeur par défaut (ou un nonce object
) et activez-la pour créer vos valeurs au moment de l'exécution. oulambda
comme paramètre par défaut et appelez-le dans un bloc try pour obtenir la valeur par défaut (c'est le genre de chose à laquelle l'abstraction lambda est destinée).La deuxième option est Nice car les utilisateurs de la fonction peuvent passer un callable, qui peut être déjà existant (comme un type
)
Je vais démontrer une structure alternative pour passer une valeur de liste par défaut à une fonction (cela fonctionne aussi bien avec les dictionnaires).
Comme d'autres l'ont abondamment commenté, le paramètre list est lié à la fonction quand il est défini, par opposition à quand il est exécuté. Comme les listes et les dictionnaires sont mutables, toute modification de ce paramètre affectera les autres appels de cette fonction. En conséquence, les appels ultérieurs à la fonction recevront cette liste partagée qui pourrait avoir été modifiée par tout autre appel à la fonction. Pire encore, deux paramètres utilisent le paramètre partagé de cette fonction en même temps, oubliant les modifications apportées par l'autre.
Mauvaise méthode (probablement ...):
def foo(list_arg=[5]):
return list_arg
a = foo()
a.append(6)
>>> a
[5, 6]
b = foo()
b.append(7)
# The value of 6 appended to variable 'a' is now part of the list held by 'b'.
>>> b
[5, 6, 7]
# Although 'a' is expecting to receive 6 (the last element it appended to the list),
# it actually receives the last element appended to the shared list.
# It thus receives the value 7 previously appended by 'b'.
>>> a.pop()
7
Vous pouvez vérifier qu'il ne s'agit que d'un seul et même objet à l'aide de id
:
>>> id(a)
5347866528
>>> id(b)
5347866528
Percez Brett Slatkin "Effective Python: 59 façons spécifiques d'écrire un meilleur python", Rubrique 20: Utilisez None
et Docstrings pour spécifier des arguments dynamiques par défaut (p. 48)
La convention pour obtenir le résultat souhaité dans Python consiste à fournir la valeur par défaut de
None
et à documenter le comportement réel dans la chaîne de documentation.
Cette implémentation garantit que chaque appel à la fonction reçoit la liste par défaut ou bien la liste transmise à la fonction.
Méthode préférée:
def foo(list_arg=None):
"""
:param list_arg: A list of input values.
If none provided, used a list with a default value of 5.
"""
if not list_arg:
list_arg = [5]
return list_arg
a = foo()
a.append(6)
>>> a
[5, 6]
b = foo()
b.append(7)
>>> b
[5, 7]
c = foo([10])
c.append(11)
>>> c
[10, 11]
Il peut y avoir des cas d’utilisation légitimes pour la "méthode incorrecte" dans lesquels le programmeur souhaitait que le paramètre de liste par défaut soit partagé, mais il est plus probable que l’exception que la règle.
Quand on fait ça:
def foo(a=[]):
...
... nous affectons l'argument a
à une liste non nommée, si l'appelant ne transmet pas la valeur de a.
Pour simplifier la discussion, donnons temporairement un nom à la liste non nommée. Qu'en est-il de pavlo
?
def foo(a=pavlo):
...
A tout moment, si l'appelant ne nous dit pas ce qu'est a
, nous réutilisons pavlo
.
Si pavlo
est modifiable, et foo
finit par le modifier, un effet que nous remarquons la prochaine fois que foo
est appelé sans spécifier a
.
Donc, voici ce que vous voyez (rappelez-vous que pavlo
est initialisé à []):
>>> foo()
[5]
Maintenant, pavlo
est [5].
L'appel de foo()
à nouveau modifie à nouveau pavlo
:
>>> foo()
[5, 5]
La spécification de a
lors de l'appel de foo()
garantit que pavlo
n'est pas touché.
>>> ivan = [1, 2, 3, 4]
>>> foo(a=ivan)
[1, 2, 3, 4, 5]
>>> ivan
[1, 2, 3, 4, 5]
Donc, pavlo
est toujours [5, 5]
.
>>> foo()
[5, 5, 5]
J'exploite parfois ce comportement au lieu du modèle suivant:
singleton = None
def use_singleton():
global singleton
if singleton is None:
singleton = _make_singleton()
return singleton.use_me()
Si singleton
n'est utilisé que par use_singleton
, j'aime le motif suivant en remplacement:
# _make_singleton() is called only once when the def is executed
def use_singleton(singleton=_make_singleton()):
return singleton.use_me()
Je l'ai utilisé pour instancier des classes de clients accédant à des ressources externes, ainsi que pour créer des bases de données ou des listes pour la mémorisation.
Étant donné que je ne pense pas que ce modèle soit bien connu, je mets un bref commentaire en garde contre de futurs malentendus.
Vous pouvez contourner cela en remplaçant l'objet (et donc le lien avec la portée):
def foo(a=[]):
a = list(a)
a.append(5)
return a
Moche, mais ça marche.
C'est peut-être vrai que:
il est tout à fait cohérent de conserver les deux caractéristiques ci-dessus tout en soulignant un autre point:
Les autres réponses, ou du moins certaines d'entre elles, font soit les points 1 et 2 mais pas 3, soit les points 3 et minimisent les points 1 et 2. Mais les trois sont vraies.
Il est peut-être vrai que changer de cheval à mi-chemin ici demanderait une casse importante et qu'il pourrait y avoir plus de problèmes en changeant Python pour gérer intuitivement l'extrait d'ouverture de Stefano. Et il se peut que quelqu'un qui connaissait bien Python les composants internes puisse expliquer un champ de mines de conséquences. Cependant,
Le comportement existant n’est pas Pythonic et Python réussit car très peu de choses sur la langue violent le principe de moindre étonnement près de cette mal. C'est un problème réel, s'il serait sage ou non de le déraciner. C'est un défaut de conception. Si vous comprenez beaucoup mieux le langage en essayant de tracer le comportement, je peux dire que C++ fait tout cela et plus encore; vous apprenez beaucoup en naviguant, par exemple, dans les erreurs de pointeur subtiles. Mais ce n’est pas Pythonic: les personnes qui s’intéressent suffisamment à Python pour persévérer face à ce comportement sont des personnes attirées par le langage, car Python a beaucoup moins de surprises que d’autres langages. Les dabbleurs et les curieux deviennent des pythonistes quand ils s’étonnent du peu de temps qu’il faut pour faire fonctionner quelque chose - et non à cause d’une conception - c’est-à-dire d’une logique cachée - qui tranche avec les intuitions des programmeurs attirés par le Python parce qu'il Just Works.
Ceci n'est pas un défaut de conception. Quiconque trébuche dessus fait quelque chose de mal.
Il y a 3 cas où je vois où vous pourriez rencontrer ce problème:
cache={}
, et on ne s'attendrait pas à ce que vous appeliez la fonction avec un argument réel.L'exemple de la question pourrait appartenir à la catégorie 1 ou 3. Il est étrange qu'il modifie et retourne la liste transmise; vous devriez choisir l'un ou l'autre.
Ce "bug" m'a donné beaucoup d'heures supplémentaires! Mais je commence à voir une utilisation potentielle (mais j'aurais aimé que ce soit au moment de l'exécution, quand même)
Je vais vous donner ce que je considère comme un exemple utile.
def example(errors=[]):
# statements
# Something went wrong
mistake = True
if mistake:
tryToFixIt(errors)
# Didn't work.. let's try again
tryToFixItAnotherway(errors)
# This time it worked
return errors
def tryToFixIt(err):
err.append('Attempt to fix it')
def tryToFixItAnotherway(err):
err.append('Attempt to fix it by another way')
def main():
for item in range(2):
errors = example()
print '\n'.join(errors)
main()
imprime ce qui suit
Attempt to fix it
Attempt to fix it by another way
Attempt to fix it
Attempt to fix it by another way
Il suffit de changer la fonction pour être:
def notastonishinganymore(a = []):
'''The name is just a joke :)'''
a = a[:]
a.append(5)
return a
Je pense que la réponse à cette question réside dans la façon dont python transmet les données au paramètre (transmission par valeur ou par référence), pas de mutabilité, ni comment python gère l'instruction "def".
Une brève introduction. Premièrement, il existe deux types de types de données en python: le type de données élémentaire simple, comme les nombres, et le type de données les objets. Deuxièmement, lors du transfert de données vers des paramètres, python transmet le type de données élémentaire par valeur, c’est-à-dire crée une copie locale de la valeur dans une variable locale, mais transmet objet par référence, c’est-à-dire des pointeurs sur l’objet.
En admettant les deux points ci-dessus, expliquons ce qui est arrivé au code python. C’est uniquement à cause du passage par référence pour les objets, mais n’a rien à voir avec mutable/immuable, ni peut-être avec le fait que la déclaration "def" n’est exécutée qu’une fois lorsqu’elle est définie.
[] est un objet, donc python passe la référence de [] à a
, c'est-à-dire que a
n'est qu'un pointeur sur [] qui se trouve dans la mémoire en tant qu'objet. Il n'y a qu'un seul exemplaire de [] avec, toutefois, de nombreuses références. Pour le premier foo (), la liste [] est remplacée par 1 par la méthode add. Mais notez qu'il n'y a qu'une seule copie de l'objet liste et que cet objet devient maintenant 1 . Lorsque vous exécutez le second foo (), la page Web effbot (les éléments ne sont plus évalués) est fausse. a
est évalué comme étant l'objet liste, bien que maintenant le contenu de l'objet soit 1 . C'est l'effet de passer par référence! Le résultat de foo (3) peut être facilement dérivé de la même manière.
Pour valider davantage ma réponse, examinons deux codes supplémentaires.
====== N ° 2 ========
def foo(x, items=None):
if items is None:
items = []
items.append(x)
return items
foo(1) #return [1]
foo(2) #return [2]
foo(3) #return [3]
[]
est un objet, donc None
(le premier est mutable tandis que le dernier est immuable. Mais la mutabilité n'a rien à voir avec la question). None est quelque part dans l'espace mais nous savons qu'il est là et qu'il n'y a qu'un seul exemplaire de None ici. Ainsi, chaque fois que foo est appelé, les éléments sont évalués (par opposition à une réponse indiquant qu’il n’est évalué qu’une fois) comme étant Aucun, pour être clair, la référence (ou l’adresse) de Aucun. Ensuite, dans le foo, l'élément est remplacé par [], c'est-à-dire qu'il pointe vers un autre objet ayant une adresse différente.
====== N ° 3 =======
def foo(x, items=[]):
items.append(x)
return items
foo(1) # returns [1]
foo(2,[]) # returns [2]
foo(3) # returns [1,3]
L'invocation de foo (1) fait que les éléments pointent vers un objet de liste [] avec une adresse, par exemple 11111111. le contenu de la liste est remplacé par 1 dans la fonction foo de la suite, mais l'adresse n'est pas changée, toujours 11111111. Alors foo (2, []) arrive. Bien que le [] dans foo (2, []) ait le même contenu que le paramètre par défaut [] lors de l'appel de foo (1), leur adresse est différente! Puisque nous fournissons explicitement le paramètre, items
doit prendre l'adresse de ce nouveau []
, par exemple 2222222, et le renvoyer après avoir apporté des modifications. Maintenant foo (3) est exécuté. étant donné que seul x
est fourni, les éléments doivent reprendre sa valeur par défaut. Quelle est la valeur par défaut? Il est défini lors de la définition de la fonction foo: l'objet liste situé dans 11111111. Par conséquent, les éléments sont considérés comme étant l'adresse 11111111 ayant un élément 1. La liste située en 2222222 contient également un élément 2, mais elle n'est pas pointée par les éléments plus. En conséquence, un appendice de 3 fera items
[1,3].
D'après les explications ci-dessus, nous pouvons constater que la page Web effbot recommandée dans la réponse acceptée n'a pas permis de fournir une réponse pertinente à cette question. Qui plus est, je pense qu’un point de la page Web effbot est faux. Je pense que le code concernant le UI.Button est correct:
for i in range(10):
def callback():
print "clicked button", i
UI.Button("button %s" % i, callback)
Chaque bouton peut contenir une fonction de rappel distincte qui affichera une valeur différente de i
. Je peux donner un exemple pour montrer ceci:
x=[]
for i in range(10):
def callback():
print(i)
x.append(callback)
Si nous exécutons x[7]()
, nous aurons 7 comme prévu et x[9]()
donnera 9, une autre valeur de i
.
TLDR: Les valeurs par défaut de définition du temps sont cohérentes et strictement plus expressives.
Définir une fonction affecte deux portées: la portée définissante contenant la fonction et la portée d'exécution contenue dans la fonction. Bien que la façon dont les blocs correspondent aux portées soit assez claire, la question est de savoir à quel def <name>(<args=defaults>):
appartient:
... # defining scope
def name(parameter=default): # ???
... # execution scope
La def name
part doit évaluer dans la portée de définition - nous voulons que name
soit disponible là, après tout. Évaluer la fonction uniquement à l'intérieur d'elle-même la rendrait inaccessible.
Comme parameter
est un nom constant, nous pouvons "l'évaluer" en même temps que def name
. Cela a également l’avantage de produire la fonction avec une signature connue sous le nom de name(parameter=...):
, au lieu d’un name(...):
nu.
Maintenant, quand évaluer default
?
La cohérence dit déjà "à la définition": tout le reste de def <name>(<args=defaults>):
est également évalué à la définition. Retarder certaines parties serait un choix étonnant.
Les deux choix ne sont pas équivalents non plus: Si default
est évalué au moment de la définition, il peut toujours affecter le temps d'exécution. Si default
est évalué au moment de l'exécution, il ne peut pas affecter le temps de définition. Le choix "à la définition" permet d'exprimer les deux cas, alors que le choix "à l'exécution" ne peut en exprimer qu'un:
def name(parameter=defined): # set default at definition time
...
def name(parameter=default): # delay default until execution time
parameter = default if parameter is None else parameter
...
Chaque autre réponse explique pourquoi il s’agit en fait d’un comportement agréable et souhaité, ou pourquoi vous ne devriez pas en avoir besoin de toute façon. Le mien est pour ceux qui sont têtus qui veulent exercer leur droit de courber le langage à leur guise, et non l'inverse.
Nous allons "corriger" ce problème avec un décorateur qui copiera la valeur par défaut au lieu de réutiliser la même instance pour chaque argument de position laissé à sa valeur par défaut.
import inspect
from copy import copy
def sanify(function):
def wrapper(*a, **kw):
# store the default values
defaults = inspect.getargspec(function).defaults # for python2
# construct a new argument list
new_args = []
for i, arg in enumerate(defaults):
# allow passing positional arguments
if i in range(len(a)):
new_args.append(a[i])
else:
# copy the value
new_args.append(copy(arg))
return function(*new_args, **kw)
return wrapper
Maintenant, redéfinissons notre fonction en utilisant ce décorateur:
@sanify
def foo(a=[]):
a.append(5)
return a
foo() # '[5]'
foo() # '[5]' -- as desired
Ceci est particulièrement intéressant pour les fonctions qui prennent plusieurs arguments. Comparer:
# the 'correct' approach
def bar(a=None, b=None, c=None):
if a is None:
a = []
if b is None:
b = []
if c is None:
c = []
# finally do the actual work
avec
# the nasty decorator hack
@sanify
def bar(a=[], b=[], c=[]):
# wow, works right out of the box!
Il est important de noter que la solution ci-dessus se casse si vous essayez d'utiliser des arguments de mots clés, comme suit:
foo(a=[4])
Le décorateur pourrait être ajusté pour permettre cela, mais nous laissons cela comme un exercice pour le lecteur;)