variable nonlocal locales inside globales def python variables scope

nonlocal - Funciones anidadas de Python alcance variable



variable local python (10)

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 declaración b = 4 comentada, este código arroja 0 1 , justo lo que cabría esperar.

Pero si descomenta esa línea, en la línea print b , obtienes el error

UnboundLocalError: local variable ''b'' referenced before assignment

Parece misterioso que la presencia de b = 4 alguna manera haga que b desaparezca 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 está asignado a adentro y que, por lo tanto, es una variable local de inner . La línea de impresión intenta imprimir el b en ese ámbito interno antes de que se le haya asignado.

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

Creo que me falta algo sobre el alcance variable de 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 yo obtengo

"nombre global ''_total'' no está definido"

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

¿Puede alguien explicarme qué me falta sobre el alcance variable python?


Cuando ejecuto su código obtengo 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 ninguna declaración global vigente, las asignaciones a los nombres siempre van al alcance más interno . Las asignaciones no copian datos, solo vinculan nombres a objetos.

Entonces, dado que la línea efectivamente dice:

_total = _total + PRICE_RANGES[key][0]

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



En lugar de declarar un objeto especial o mapa o matriz, también se puede usar un atributo de función. Esto hace que el alcance 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. Entonces uno debe estar atento al enhebrado y la recursión.


Esta es una variación de la solución de redman, pero usa un espacio de nombre propio 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 si el uso de un objeto de clase de esta manera se considera un truco feo o una técnica de codificación 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 acerca de la eficiencia de esta solución en tiempo de ejecución.


Más desde un punto de vista filosófico, una respuesta podría ser "si tienes problemas con el espacio de nombres, ¡dale 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 trasladar variables entre varias funciones de nivel superior (sin duda, habrá más que solo get_order_total ) .

Preservar 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 truco que es una variante de la idea de lista en otra respuesta, pero quizás más clara,

def outer(): order = {''total'': 0} def inner(): order[''total''] += 42 inner() return order[''total''] print outer()


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()


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

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

En cambio, yo haría esto:

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

De esta forma, X nunca es una variable local


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

Aquí hay un enfoque de @Hans modificado, excepto que utilizo un atributo de la función interna, en lugar de usar el atributo externo. Esto debería ser más compatible con la recursión, y tal vez incluso multihilo:

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

Así que, de hecho, parece mejor hacer que sean 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 le recuerda al lector que la variable se comparte entre los ámbitos de las funciones interna y externa. Un pequeño inconveniente para la legibilidad es que el inner.attribute solo se puede establecer sintácticamente después de la def inner(): ...


>>> 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'': <function recurse at 0xb76baed4>, ''_i'': <dictionary-keyiterator object at 0xb6473e64>, ''quantity'': 20} {''__builtins__'': <module ''__builtin__'' (built-in)>, ''PRICE_RANGES'': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, ''__package__'': None, ''s'': <function s at 0xb646adf4>, ''get_order_total'': <function get_order_total at 0xb646ae64>, ''__name__'': ''__main__'', ''__doc__'': None} {''recurse'': <function recurse at 0xb76baed4>, ''_i'': <dictionary-keyiterator object at 0xb6473e64>, ''quantity'': 20} {''__builtins__'': <module ''__builtin__'' (built-in)>, ''PRICE_RANGES'': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, ''__package__'': None, ''s'': <function s at 0xb646adf4>, ''get_order_total'': <function get_order_total at 0xb646ae64>, ''__name__'': ''__main__'', ''__doc__'': None} {''recurse'': <function recurse at 0xb76baed4>, ''_i'': <dictionary-keyiterator object at 0xb6473e64>, ''quantity'': 20} {''__builtins__'': <module ''__builtin__'' (built-in)>, ''PRICE_RANGES'': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, ''__package__'': None, ''s'': <function s at 0xb646adf4>, ''get_order_total'': <function get_order_total at 0xb646ae64>, ''__name__'': ''__main__'', ''__doc__'': None} {''recurse'': <function recurse at 0xb76baed4>, ''_i'': <dictionary-keyiterator object at 0xb6473e64>, ''quantity'': 20} {''__builtins__'': <module ''__builtin__'' (built-in)>, ''PRICE_RANGES'': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, ''__package__'': None, ''s'': <function s at 0xb646adf4>, ''get_order_total'': <function get_order_total at 0xb646ae64>, ''__name__'': ''__main__'', ''__doc__'': None} Traceback (most recent call last): File "<pyshell#32>", line 1, in <module> get_order_total(20) File "<pyshell#31>", line 18, in get_order_total res = recurse(_i) File "<pyshell#31>", line 13, in recurse return recurse(_i) File "<pyshell#31>", line 13, in recurse return recurse(_i) File "<pyshell#31>", line 12, in recurse total += PRICE_RANGES[key][0] UnboundLocalError: local variable ''total'' referenced before assignment >>>

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