2012-08-29 18 views
8

Acabo de enterarme del decorador de python @, es genial, pero pronto descubrí que mi código modificado presentaba problemas extraños.Función de cierre de Python que pierde el acceso variable externo

def with_wrapper(param1): 
    def dummy_wrapper(fn): 
     print param1 
     param1 = 'new' 
     fn(param1) 
    return dummy_wrapper 

def dummy(): 
    @with_wrapper('param1') 
    def implementation(param2): 
     print param2 

dummy() 

que depurarlo, que arroja una excepción en param1 de impresión

UnboundLocalError: local variable 'param1' referenced before assignment 

Si quito param1 = 'new' esta línea, sin ninguna operación de modificación (enlace al nuevo objeto) sobre las variables del ámbito de aplicación externa, esta rutina podría funcionar

¿Significa que solo hice una copia de las variables de ámbito externo y luego realicé la modificación?


Gracias Delnan, es esencial para el cierre. respuesta probable de aquí: What limitations have closures in Python compared to language X closures?

código similar como:

def e(a): 
    def f(): 
     print a 
     a = '1' 
    f() 
e('2') 

Y también esto parece anterior variable global molesto:

a = '1' 
def b(): 
    #global a 
    print a 
    a = '2' 
b() 

Este es fijado por añadir símbolo global. Pero para el cierre, no se encontró ese símbolo. Gracias unutbu, Python 3 nos dio no local.

Sé desde arriba que el acceso directo a la variable externa es de solo lectura. pero es un poco incómodo ver la variable de lectura precedida (imprimir var) también se ve afectada.

+0

posible duplicado de [¿Qué limitaciones tienen cierres en Python en comparación con los cierres de idioma X?] (http://stackoverflow.com/questions/141642/what-limitations-have-closures-in-python-compared-to-language-x-closures) – delnan

+0

Esto tiene absolutamente nada que ver con decoradores por cierto. – delnan

+0

Sí, esto sucede en cierres. así: def (a) e: def f(): impresión de un a = '1' f() e ('123') –

Respuesta

11

Cuando Python analiza una función, se observa cada vez que encuentra una variable utilizada en el lado izquierdo de una asignación, tales como

param1 = 'new' 

Se supone que todas estas variables son locales a la función. Así que cuando preceden a esta asignación con

print param1 

un error se produce porque Python no tiene un valor para esta variable local en el momento en que se ejecuta la instrucción de impresión.


En python3 se puede solucionar este problema al declarar que param1 es no local:

def with_wrapper(param1): 
    def dummy_wrapper(fn): 
     nonlocal param1 
     print param1 
     param1 = 'new' 
     fn(param1) 
    return dummy_wrapper 

En python2 que tienen que recurrir a un truco, tal como pasar param1 dentro de una lista (o algún otro objeto mutable):

def with_wrapper(param1_list): 
    def dummy_wrapper(fn): 
     print param1_list[0] 
     param1_list[0] = 'new' # mutate the value inside the list 
     fn(param1_list[0]) 
    return dummy_wrapper 

def dummy(): 
    @with_wrapper(['param1']) # <--- Note we pass a list here 
    def implementation(param2): 
     print param2 
+0

¿Y cuál es la mejor manera de "arreglarlo"? – Silox

+1

... poner la instrucción 'print' después de la tarea? – kindall

+0

Gracias por explicarme. Pero no tengo idea de por qué param1 = 'nuevo' reasignar el param externo, pero para hacer una var local. –

0

Se asigna param1 en la función, lo que hace param1 una variable local. Sin embargo, no se ha asignado en el momento en que lo está imprimiendo, por lo que obtiene un error. Python no recurre a buscar variables en ámbitos externos.

+0

_print param1_ es un ejemplo, en código real, cualquier referencia sobre param1 está prohibida cuando param1 = xx existe –

1

Si quiere capturar una variable del alcance externo en Python 2.x, usar global también es una opción (con las condiciones habituales, pero útil para situaciones temporales o código exploratorio).

Mientras que la siguiente tirará (asignación de outer1 dentro interior hace que sea local y por lo tanto sin límites en el caso de condición):

def outer(): 
    outer1 = 1 
    def inner(): 
     if outer1 == 1: 
      outer1 = 2 
      print('attempted to accessed outer %d' % outer1) 

Esto no:

def outer(): 
    global outer1 
    outer1 = 1 
    def inner(): 
     global outer1 
     if outer1 == 1: 
      outer1 = 2 
      print('accessed outer %d' % outer1) 
Cuestiones relacionadas