web-dev-qa-db-fra.com

"Le moindre étonnement" et l'argument invariable par défaut

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.

2395
Stefano Borini

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.

1524
rob

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é.

260
Eli Courtwright

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 [...]

228
glglgl

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):
109
Utaal

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.

81
Lennart Regebro

Pourquoi ne pas vous introspecter?

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).

Fonction avant exécution:

>>> 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.

Fonction après exécution:

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éé.

57

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.

57
Brian

5 points en défense de Python

  1. 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.

  2. 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.

  3. 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.)

  4. 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é.

  5. 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.

53
Lutz Prechelt

Ce comportement est facile à expliquer par:

  1. la déclaration de fonction (classe, etc.) est exécutée une seule fois, créant tous les objets de valeur par défaut
  2. tout est passé par référence

Alors:

def x(a=0, b=[], c=[], d=0):
    a = a + 1
    b = b + [1]
    c.append(1)
    print a, b, c
  1. a ne change pas - chaque appel d'affectation crée un nouvel objet int - un nouvel objet est imprimé
  2. b ne change pas - le nouveau tableau est construit à partir de la valeur par défaut et imprimé
  3. c changements - l'opération est effectuée sur le même objet - et il est imprimé
49
ymv

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.

33
Glenn Maynard

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 .

31
hynekcer

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.

28
Ben

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
26
Stéphane

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 ):

#1

0 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.

25
Jason Baker

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.

23
Baczek

Python: l'argument par défaut mutable

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.

Code équivalent:

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

Manifestation

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

  • voyant que la liste est créée avant que la fonction ait fini de compiler un objet fonction,
  • en observant que l'identifiant est le même chaque fois que la liste est référencée,
  • en observant que la liste reste modifiée lorsque la fonction qui l'utilise est appelée une seconde fois,
  • en observant l'ordre dans lequel la sortie est imprimée à partir de la source (que j'ai numérotée pour vous):

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']

Cela viole-t-il le principe de "moindre surprise"?

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.

L'instruction habituelle pour les nouveaux Python:

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
23
Aaron Hall

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]
19
hugo24

Ce comportement n’est pas surprenant si vous tenez compte des éléments suivants:

  1. Le comportement des attributs de classe en lecture seule lors des tentatives d’attribution, et cela
  2. Les fonctions sont des objets (bien expliqués dans la réponse acceptée).

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é:

  • Lorsque l'objet de fonction foo est instancié, foo.func_defs[0] est défini sur None, un objet immuable.
  • Lorsque la fonction est exécutée avec des valeurs par défaut (sans paramètre spécifié pour L dans l'appel de fonction), foo.func_defs[0] (None) est disponible dans la portée locale en tant que L.
  • Sur L = [], l'affectation ne peut aboutir à foo.func_defs[0], car cet attribut est en lecture seule.
  • Par (1) , , une nouvelle variable locale également nommée 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.
19
Dmitry Minkovsky

Les solutions ici sont:

  1. Utilisez None comme valeur par défaut (ou un nonce object) et activez-la pour créer vos valeurs au moment de l'exécution. ou
  2. Utilisez un paramètre lambda 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)

17
Marcin

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.

17
Alexander

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]
16
Saish

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.

16
bgreen-litl

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.

15
jdborg

C'est peut-être vrai que:

  1. Quelqu'un utilise toutes les fonctionnalités de langue/bibliothèque et
  2. Changer le comportement ici serait mal avisé, mais

il est tout à fait cohérent de conserver les deux caractéristiques ci-dessus tout en soulignant un autre point:

  1. C'est une fonctionnalité déroutante et c'est malheureux en Python.

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.

12
Christos Hayward

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:

  1. Vous avez l'intention de modifier l'argument en tant qu'effet secondaire de la fonction. Dans ce cas, il jamais logique d'avoir un argument par défaut. La seule exception est lorsque vous abusez de la liste d'arguments pour avoir des attributs de fonction, par exemple. cache={}, et on ne s'attendrait pas à ce que vous appeliez la fonction avec un argument réel.
  2. Vous avez l'intention de laisser l'argument non modifié, mais vous l'avez accidentellement l'a fait le modifier. C'est un bug, corrigez-le.
  3. Vous avez l'intention de modifier l'argument pour l'utiliser dans la fonction, mais vous ne vous attendiez pas à ce que la modification soit visible en dehors de la fonction. Dans ce cas, vous devez faire une copie de l'argument, que ce soit par défaut ou non! Python n'est pas un langage appel par valeur, il ne crée donc pas la copie pour vous, vous devez être explicite à ce sujet.

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.

10
Mark Ransom

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
9
Norfeldt

Il suffit de changer la fonction pour être:

def notastonishinganymore(a = []): 
    '''The name is just a joke :)'''
    a = a[:]
    a.append(5)
    return a
8
ytpillai

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.

7
user2384994

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
    ...
5
MisterMiyagi

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;)

2
Przemek D