2010-11-03 17 views
3

J'écris une application où les Tags sont liables et il est nécessaire de récupérer toute la chaîne des Tags liés. L'auto-référence n'est pas autorisée. L'exécution du code ci-dessous se termine avec des résultats très étranges:Pourquoi la définition d'une valeur de paramètre par défaut fait-elle de cette fonction une fermeture?

class Tag(object): 
    def __init__(self, name): 
    self.name = name 
    self.links = [] 

    def __repr__(self): 
    return "<Tag {0}>".format(self.name) 

    def link(self, tag): 
    self.links.append(tag) 


def tag_chain(tag, known=[]): 
    chain = [] 
    if tag not in known: 
    known.append(tag) 
    print "Known: {0}".format(known) 

    for link in tag.links: 
    if link in known: 
     continue 
    else: 
     known.append(link) 
    chain.append(link) 
    chain.extend(tag_chain(link, known)) 
    return chain 

a = Tag("a") 
b = Tag("b") 
c = Tag("c") 
a.link(b) 
b.link(c) 
c.link(a) 

o = tag_chain(a) 
print "Result:", o 
print "------------------" 
o = tag_chain(a) 
print "Result:", o 

Résultats:

Known: [<Tag a>] 
Known: [<Tag a>, <Tag b>] 
Known: [<Tag a>, <Tag b>, <Tag c>] 
Result: [<Tag b>, <Tag c>] 
------------------ 
Known: [<Tag a>, <Tag b>, <Tag c>] 
Result: [] 

donc, en quelque sorte, je l'ai créé accidentellement une fermeture. Pour autant que je peux voir, connu aurait dû sortir de la portée et est mort une fois l'appel de fonction terminée.

Si je change la définition de chain_tags() pour définir pas une valeur par défaut, le problème disparaît:

... 
def tag_chain(tag, known): 
... 
o = tag_chain(a, []) 
print "Result:", o 
print "------------------" 
o = tag_chain(a, []) 
print "Result:", o 

Pourquoi?

Répondre

9

Ceci est une erreur commune en Python:

def tag_chain(tag, known=[]): 
    # ... 

known=[] ne signifie pas que si connu est non ravitaillée, faire une liste vide; en fait, il se connecte à une liste "anonyme". Chaque fois que connu par défaut à cette liste, c'est la même liste.

Le modèle typique de faire ce que vous vouliez ici, est:

def tag_chain(tag, known=None): 
    if known is None: 
     known = [] 
    # ... 

initialisant correctement known à une liste vide si elle n'est pas fourni.

+0

Merci pour la réponse claire. Je suis habitué à ce que Python soit beaucoup plus explicite que ça. Si quelqu'un connaît la raison d'être de cette décision, je serais vraiment intéressé de le voir. –

3

Peut-être comme eplanation supplémentaire ce qui se passe: les paramètres par défaut sont simplement stockés sur l'objet de la fonction elle-même (c.-à-tag_chain.func_defaults dans AP2) et utilisés pour étendre l'argument en cas de besoin:

>>> def x(a=['here']): 
...  a.append(a[-1]*2) 
... 
>>> x 
<function x at 0x0053DB70> 
>>> x.func_defaults 
(['here'],) 

Dans cet exemple, vous pouvez regarder la liste par défaut croître là:

>>> x() 
>>> x.func_defaults 
(['here', 'herehere'],) 
>>> x() 
>>> x.func_defaults 
(['here', 'herehere', 'herehereherehere'],) 

Modifier les arguments par défaut est un peu comme changer les variables de classe.

+0

Très utile. Merci! –