Asignación a la variable desde la función principal: “Variable local referenciada antes de la asignación”

Para el siguiente código de Python 2.7:

#!/usr/bin/python def funcA(): print "funcA" c = 0 def funcB(): c += 3 print "funcB", c def funcC(): print "funcC", c print "c", c funcB() c += 2 funcC() c += 2 funcB() c += 2 funcC() print "end" funcA() 

Obtuve el siguiente error:

 File "./a.py", line 9, in funcB c += 3 UnboundLocalError: local variable 'c' referenced before assignment 

Pero cuando comento la línea c += 3 en funcB , obtengo el siguiente resultado:

 funcA c 0 funcB 0 funcC 2 funcB 4 funcC 6 end 

¿No se está accediendo a c en ambos casos de += en funcB y = en funcC ? ¿Por qué no arroja error para uno pero no para el otro?

No tengo la opción de hacer c una variable global y luego declarar global c en funcB . De todos modos, el punto no es obtener c incrementado en funcB sino por qué es un error de lanzamiento para funcB y no para funcC mientras que ambos están accediendo a una variable que es local o global.

Lo que está viendo aquí es la diferencia entre acceder y asignar variables. En Python 2.x solo puede asignar variables en el ámbito más interno o global (este último se realiza mediante el uso de la statement global). Puede acceder a las variables en cualquier ámbito adjunto, pero no puede acceder a una variable en un ámbito envolvente y luego asignárselas en el ámbito más interno o global.

Lo que esto significa es que si hay alguna asignación a un nombre dentro de una función, ese nombre ya debe estar definido en el ámbito más interno antes de acceder al nombre (a menos que se use la statement global). En su código, la línea c += 3 es esencialmente equivalente a lo siguiente:

 tmp = c c = tmp + 3 

Debido a que hay una asignación a c en la función, cualquier otra aparición de c en esa función solo buscará en el ámbito local para funcB . Esta es la razón por la que ve el error, está intentando acceder a c para obtener su valor actual para += , pero en el ámbito local no se ha definido aún.

En Python 3, podría solucionar este problema utilizando la statement no local , que le permite asignar variables que no están en el ámbito actual, pero tampoco están en el ámbito global.

Su código se vería así, con una línea similar en la parte superior de funcC :

  def funcB(): nonlocal c c += 3 ... 

En Python 2.x, esta no es una opción, y la única forma de cambiar el valor de una variable no local es si es mutable.

La forma más sencilla de hacerlo es envolver su valor en una lista, y luego modificar y acceder al primer elemento de esa lista en todos los lugares en los que anteriormente había utilizado el nombre de la variable:

 def funcA(): print "funcA" c = [0] def funcB(): c[0] += 3 print "funcB", c[0] def funcC(): c[0] = 5 print "funcC", c[0] print "c", c[0] funcB() funcC() funcB() funcC() print "end" funcA() 

… y la salida:

 funcA c 0 funcB 3 funcC 5 funcB 8 funcC 5 end 

¿No se está accediendo a ‘c’ en ambos casos de ‘+ =’ en funcB y ‘=’ en funcC?

No, funcC una nueva variable, también llamada c . = es diferente a este respecto de += .

Para obtener el comportamiento que (probablemente) desea, envuelva la variable en una lista de un solo elemento:

 def outer(): c = [0] def inner(): c[0] = 3 inner() print c[0] 

imprimirá 3 .

Editar : querrás pasar c como un argumento. Python 2 no tiene otra forma, AFAIK, para obtener el comportamiento deseado. Python 3 introduce la palabra clave nonlocal para estos casos.

1) ¿No se está accediendo a c en ambos casos de += en funcB y = en funcC ?

No, porque c += 3 es lo mismo que:

 c = c + 3 ^ | and funcB does not know what this c is 

2) No tengo la opción de hacer c una variable global y luego declarar global c en funcB .

Por favor no hagas eso, solo cambia:

 def funcB(): 

con:

 def funcB(c): 

y llame a funcB(c) más adelante en su código.

Nota: También debe considerar definir funcB y funcC fuera de funcA

Prueba esto:

 def funcA(): print "funcA" c = 0 def funcB(c): c += 3 print "funcB", c def funcC(c): c = 5 print "funcC", c print "c", c funcB(c) funcC(c) funcB(c) funcC(c) print "end" funcA() 

Y si quieres recordar el valor de c entonces:

 def funcA(): print "funcA" c = 0 def funcB(c): c += 3 print "funcB", c return c def funcC(c): c = 5 print "funcC", c return c print "c", c c = funcB(c) c = funcC(c) c = funcB(c) c = funcC(c) print "end" funcA() 

que producirá:

 funcA c 0 funcB 3 funcC 5 funcB 8 funcC 5 end C:\Python26\ 

Otra solución sucia que, sin embargo, no requiere que c globalice. Todo igual, pero:

 def funcB(): globals()['c'] += 3 print "funcB", c