Funciones anidadas de Python variable scope

He leído casi todas las otras preguntas sobre el tema, pero mi código aún no funciona.

Creo que me estoy perdiendo algo sobre el scope de la variable python.

Aquí está mi código:

PRICE_RANGES = { 64:(25, 0.35), 32:(13, 0.40), 16:(7, 0.45), 8:(4, 0.5) } def get_order_total(quantity): global PRICE_RANGES _total = 0 _i = PRICE_RANGES.iterkeys() def recurse(_i): try: key = _i.next() if quantity % key != quantity: _total += PRICE_RANGES[key][0] return recurse(_i) except StopIteration: return (key, quantity % key) res = recurse(_i) 

Y me pongo

“el nombre global ‘_total’ no está definido”

Sé que el problema está en la tarea _total , pero no puedo entender por qué. ¿No debería recurse() tener acceso a las variables de la función padre?

¿Puede alguien explicarme lo que me estoy perdiendo del scope de la variable Python?

Cuando ejecuto tu código me sale este error:

 UnboundLocalError: local variable '_total' referenced before assignment 

Este problema es causado por esta línea:

 _total += PRICE_RANGES[key][0] 

La documentación sobre Ámbitos y espacios de nombres dice esto:

Una peculiaridad especial de Python es que, si no hay global statement global en vigencia, las asignaciones a los nombres siempre entran en el ámbito más interno . Las asignaciones no copian datos, solo unen nombres a objetos.

Así que ya que la línea está efectivamente diciendo:

 _total = _total + PRICE_RANGES[key][0] 

crea _total en el espacio de nombres de recurse() . Dado que _total es nuevo y no asignado, no puede usarlo en la adición.

Aquí hay una ilustración que llega a la esencia de la respuesta de David.

 def outer(): a = 0 b = 1 def inner(): print a print b #b = 4 inner() outer() 

Con la statement b = 4 comentada, este código da como resultado 0 1 , justo lo que cabría esperar.

Pero si elimina el comentario de esa línea, en la línea de print b , obtendrá el error.

 UnboundLocalError: local variable 'b' referenced before assignment 

Parece misterioso que la presencia de b = 4 pueda hacer que b desaparezca de alguna manera en las líneas que lo preceden. Pero el texto que cita David explica por qué: durante el análisis estático, el intérprete determina que b se asigna en inner y que, por lo tanto, es una variable local de inner . La línea de impresión intenta imprimir la b en ese ámbito interno antes de que se haya asignado.

En Python 3, puede usar la statement nonlocal para acceder a ámbitos no locales, no globales.

En lugar de declarar un objeto especial o un mapa o una matriz, también se puede usar un atributo de función. Esto hace que el scope de la variable sea realmente claro.

 def sumsquares(x,y): def addsquare(n): sumsquares.total += n*n sumsquares.total = 0 addsquare(x) addsquare(y) return sumsquares.total 

Por supuesto, este atributo pertenece a la función (definición), y no a la llamada de función. Así que hay que tener en cuenta los hilos y la recursión.

Esta es una variación de la solución de redman, pero utilizando un espacio de nombres adecuado en lugar de una matriz para encapsular la variable:

 def foo(): class local: counter = 0 def bar(): print(local.counter) local.counter += 1 bar() bar() bar() foo() foo() 

No estoy seguro de que usar un objeto de clase de esta manera se considere un truco feo o una técnica de encoding adecuada en la comunidad de Python, pero funciona bien en Python 2.xy 3.x (probado con 2.7.3 y 3.2.3 ). Tampoco estoy seguro de la eficiencia en tiempo de ejecución de esta solución.

Probablemente haya obtenido la respuesta a su pregunta. Pero quería indicar una forma en la que usualmente soluciono esto y es mediante el uso de listas. Por ejemplo, si quiero hacer esto:

 X=0 While X<20: Do something. .. X+=1 

En lugar de eso haría esto:

 X=[0] While X<20: Do something.... X[0]+=1 

De esta manera X nunca es una variable local.

Si bien solía usar el enfoque basado en listas de @redman, no es óptimo en términos de legibilidad.

Aquí hay un enfoque modificado de @Hans, excepto que uso un atributo de la función interna, en lugar de la externa. Esto debería ser más compatible con la recursión, y quizás incluso con multiproceso:

 def outer(recurse=2): if 0 == recurse: return def inner(): inner.attribute += 1 inner.attribute = 0 inner() inner() outer(recurse-1) inner() print "inner.attribute =", inner.attribute outer() outer() 

Esto imprime:

 inner.attribute = 3 inner.attribute = 3 inner.attribute = 3 inner.attribute = 3 

Si s/inner.attribute/outer.attribute/g , obtenemos:

 outer.attribute = 3 outer.attribute = 4 outer.attribute = 3 outer.attribute = 4 

Entonces, de hecho, parece mejor convertirlos en los atributos de la función interna.

Además, parece sensato en términos de legibilidad: porque entonces la variable se relaciona conceptualmente con la función interna, y esta notación recuerda al lector que la variable se comparte entre los ámbitos de las funciones interna y externa. Un ligero inconveniente para la legibilidad es que el inner.attribute solo puede establecerse de forma sintáctica después de la def inner(): ...

Más desde un punto de vista filosófico, una respuesta podría ser “si tiene problemas con el espacio de nombres, déle un espacio de nombres propio”.

Proporcionarlo en su propia clase no solo le permite encapsular el problema, sino que también facilita las pruebas, elimina esos molestos globales y reduce la necesidad de cambiar variables entre varias funciones de nivel superior (sin duda habrá más que solo get_order_total ) .

Preservando el código del OP para enfocarse en el cambio esencial,

 class Order(object): PRICE_RANGES = { 64:(25, 0.35), 32:(13, 0.40), 16:(7, 0.45), 8:(4, 0.5) } def __init__(self): self._total = None def get_order_total(self, quantity): self._total = 0 _i = self.PRICE_RANGES.iterkeys() def recurse(_i): try: key = _i.next() if quantity % key != quantity: self._total += self.PRICE_RANGES[key][0] return recurse(_i) except StopIteration: return (key, quantity % key) res = recurse(_i) #order = Order() #order.get_order_total(100) 

Como PS, un hack que es una variante de la idea de la lista en otra respuesta, pero quizás más claro,

 def outer(): order = {'total': 0} def inner(): order['total'] += 42 inner() return order['total'] print outer() 
 >>> def get_order_total(quantity): global PRICE_RANGES total = 0 _i = PRICE_RANGES.iterkeys() def recurse(_i): print locals() print globals() try: key = _i.next() if quantity % key != quantity: total += PRICE_RANGES[key][0] return recurse(_i) except StopIteration: return (key, quantity % key) print 'main function', locals(), globals() res = recurse(_i) >>> get_order_total(20) main function {'total': 0, 'recurse': , '_i': , 'quantity': 20} {'__builtins__': , 'PRICE_RANGES': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, '__package__': None, 's': , 'get_order_total': , '__name__': '__main__', '__doc__': None} {'recurse': , '_i': , 'quantity': 20} {'__builtins__': , 'PRICE_RANGES': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, '__package__': None, 's': , 'get_order_total': , '__name__': '__main__', '__doc__': None} {'recurse': , '_i': , 'quantity': 20} {'__builtins__': , 'PRICE_RANGES': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, '__package__': None, 's': , 'get_order_total': , '__name__': '__main__', '__doc__': None} {'recurse': , '_i': , 'quantity': 20} {'__builtins__': , 'PRICE_RANGES': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, '__package__': None, 's': , 'get_order_total': , '__name__': '__main__', '__doc__': None} Traceback (most recent call last): File "", line 1, in  get_order_total(20) File "", line 18, in get_order_total res = recurse(_i) File "", line 13, in recurse return recurse(_i) File "", line 13, in recurse return recurse(_i) File "", line 12, in recurse total += PRICE_RANGES[key][0] UnboundLocalError: local variable 'total' referenced before assignment >>> 

como ve, el total está en el scope local de la función principal, pero no está en el scope local de recurse (obviamente) pero tampoco está en el scope global, porque está definido solo en el scope local de get_order_total

Mi camino alrededor …

 def outer(): class Cont(object): var1 = None @classmethod def inner(cls, arg): cls.var1 = arg Cont.var1 = "Before" print Cont.var1 Cont.inner("After") print Cont.var1 outer()