2010-03-25 8 views
8

pourquoi cela fonctionne:erreur de cadrage dans la fermeture récursive

def function1():                            
     a = 10                              
     def function2(): 
      print a 
     function2() 

mais cela ne fait pas:

def function1(): 
    a = 10 
    def function2(): 
     print a 
     a -= 1 
     if a>0: 
      function2() 
    function2() 

Je reçois cette erreur:

UnboundLocalError: local variable 'a' referenced before assignment 

Répondre

13

L'erreur ne semble pas être très descriptif du problème racine. Mike explique les messages mais cela n'explique pas la cause première. Le problème réel est qu'en python vous ne pouvez pas assigner à des variables fermées. Donc, dans la fonction2 'a' est en lecture seule. Lorsque vous l'affectez, vous créez une nouvelle variable qui, comme Mike le fait remarquer, vous lisez avant d'écrire.

Si vous souhaitez attribuer à la variable externe de la portée intérieure vous devez tricher comme ceci:

def function1(): 
    al = [10] 
    def function2(): 
     print al[0] 
     al[0] -= 1 
     if al[0]>0: 
      function2() 
    function2() 

Alors al est immuable, mais son contenu ne sont pas et vous pouvez les changer sans créer nouvelle variable.

+2

En effet, c'est le point clé dans la conception de cette fonction vous ne pouvez pas attribuer aux champs non locaux. (Note: 'al' est * mutable *, c'est pourquoi cela fonctionne.) –

+2

Je pense qu'il est important, par souci de clarté, de faire la distinction entre al la variable et les valeurs que contient al. Cela revient toujours à des pointeurs pour moi alors laissez-moi vous dire ceci; Vous ne pouvez pas faire référence à une nouvelle liste, mais vous pouvez modifier le contenu de la liste vers laquelle elle pointe. al -> [v1, v2, v3] al ne peut pas être modifié mais v1, v2 et v3 peuvent être modifiés. Mike a tout à fait raison de dire que cela rend tout mutable parce que dans notre terminologie, al * est * la liste et non le pointeur de la liste. – charlieb

+0

+1 très belle réponse. –

2

Dans l'extrait non-travail , vous affectez à a lorsque vous dites "a -= 1". Pour cette raison, à l'intérieur de function2, a est local à cette portée, pas pris la portée englobante. Les fermetures de Python sont lexicales - elles ne recherchent pas dynamiquement le a dans le cadre de function2 et s'il n'a pas été assigné, allez le chercher dans le cadre de function1. Notez que cela ne dépend pas de la récursivité ou de l'utilisation des fermetures du tout. Prenons l'exemple de cette fonction

def foo(): 
    print a 
    a = 4 

L'appeler vous obtiendrez un UnboundLocalError aussi. (Sans a = 4, il utilisera soit le a global, soit, s'il n'y en a pas, NameError.) Étant donné que a est potentiellement affecté dans cette portée, il est local.


Si je concevais cette fonction, je pourrais utiliser une approche plus comme

def function1(): 
    a = 10 
    def function2(a=a): 
     print a 
     a -= 1 
     if a > 0: 
      function2(a) 
    function2() 

(ou bien sûr for a in xrange(10, -1, -1): print a ;-))

5

Il est à noter qu'il s'agit d'un problème de syntaxe en Python. Python lui-même (au niveau du bytecode) peut très bien affecter ces variables; il n'y a simplement pas de syntaxe dans 2.x pour indiquer que vous voulez le faire. Cela suppose que si vous attribuez une variable à un niveau d'imbrication, vous voulez dire qu'il s'agit d'une variable locale.

Ceci est une énorme lacune; pouvoir assigner aux fermetures est fondamental. J'ai travaillé plusieurs fois avec le piratage de charlieb.

Python 3 corrige cela avec le gauchement nom de mot-clé « nonlocal »:

def function1(): 
    a = 10 
    def function2(): 
     nonlocal a 
     print(a) 
     a -= 1 
     if a>0: 
      function2() 
    function2() 
function1() 

Il est très pauvre que cette syntaxe est seulement disponible en 3.x; la plupart des gens sont bloqués dans 2.x, et doivent continuer à utiliser des hacks pour contourner ce problème.Cela doit être rétroporté à 2.x.

http://www.python.org/dev/peps/pep-3104/

Questions connexes