UnboundLocalError con ámbitos de función nesteds

Tengo código como este (simplificado):

def outer(): ctr = 0 def inner(): ctr += 1 inner() 

Pero ctr causa un error:

 Traceback (most recent call last): File "foo.py", line 9, in  outer() File "foo.py", line 7, in outer inner() File "foo.py", line 5, in inner ctr += 1 UnboundLocalError: local variable 'ctr' referenced before assignment 

¿Cómo puedo arreglar esto? Pensé que los ámbitos nesteds me habrían permitido hacer esto. Lo he intentado con ‘global’, pero todavía no funciona.

Si está usando Python 3, puede usar la statement nonlocal para habilitar la revinculación de un nombre no local:

 def outer(): ctr = 0 def inner(): nonlocal ctr ctr += 1 inner() 

Si está utilizando Python 2, que no tiene una función no nonlocal , debe realizar su incremento sin volver a vincular el nombre de barra (manteniendo el contador como un elemento o atributo de algún nombre de barra, no como un nombre de barra en sí). Por ejemplo:

 ... ctr = [0] def inner(): ctr[0] += 1 ... 

y, por supuesto, usa ctr[0] donde estés usando bare ctr ahora en otro lugar.

De http://www.devshed.com/c/a/Python/Nested-Functions-in-Python/1/

El código en el cuerpo de una función anidada puede acceder (pero no volver a vincular) las variables locales de una función externa, también conocidas como variables libres de la función anidada.

Por lo tanto, necesitarías pasar ctr al inner explícitamente.

La explicación

Cuando se asigna un valor a una variable dentro de una función, Python considera esa variable como una variable local de esa función. Dado que la instrucción ctr += 1 incluye una asignación a ctr , python piensa que ctr es local a la función inner . En consecuencia, ni siquiera intenta ver el valor de la variable ctr que se ha definido en el outer . Lo que Python ve es esencialmente esto:

 def inner(): ctr = ctr + 1 

Y creo que todos podemos estar de acuerdo en que este código causaría un error, ya que se está accediendo a ctr antes de que se haya definido.

(Consulte también esta pregunta para obtener más detalles sobre cómo Python decide el scope de una variable).

La Solución (en python 3)

Python 3 ha introducido la statement nonlocal , que funciona de manera muy similar a la statement global , pero nos permite acceder a las variables de la función circundante (en lugar de las variables globales). Simplemente agregue un nonlocal ctr en la parte superior de la función inner y el problema desaparecerá:

 def outer(): ctr = 0 def inner(): nonlocal ctr ctr += 1 inner() 

La solución (en python 2)

Dado que la statement no nonlocal no existe en Python 2, tenemos que ser astutos. Hay dos soluciones fáciles:

  • Eliminando todas las asignaciones a ctr

    Como Python solo considera ctr una variable local porque hay una asignación a esa variable, el problema desaparecerá si eliminamos todas las asignaciones al nombre ctr . Pero, ¿cómo podemos cambiar el valor de la variable sin asignársela? Fácil: envolvemos la variable en un objeto mutable, como una lista. Luego podemos modificar esa lista sin asignar un valor al nombre ctr :

     def outer(): ctr = [0] def inner(): ctr[0] += 1 inner() 
  • Pasando ctr como argumento al inner

     def outer(): ctr = 0 def inner(ctr): ctr += 1 return ctr ctr = inner(ctr) 

¿Qué hay de declarar ctr fuera de outer (es decir, en el ámbito global), o cualquier otra clase / función? Esto hará que la variable sea accesible y escribible.