tutorial girona espaƱol descargar curso python python-2.7 eval python-3.4

girona - Variables capturadas en "eval" en Python



qgis manual (3)

Tengo problemas para entender la semántica de "eval ()" y "exec" en Python. (Todo el código en esta pregunta se comporta de la misma manera en Python 2.7.8 y Python 3.4.2). La documentation para "eval" dice:

Si se omiten [locals y globals], la expresión se ejecuta en el entorno donde se llama a eval ().

Hay un lenguaje similar para "exec". Claramente no entiendo esta oración porque esperaría que las cuatro funciones definidas por el siguiente programa hagan lo mismo.

def h(x): ls = locals() exec(''def i(y): return (w, x, y)'', globals(), ls) i = ls[''i''] def j(y): return (w, x, y) k = eval(''lambda y: (w, x, y)'') l = lambda y: (w, x, y) return i, j, k, l w = 1 i, j, k, l = h(2)

Ellos no.

>>> i(3) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 1, in i NameError: name ''x'' is not defined >>> j(3) (1, 2, 3) >>> k(3) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 1, in <lambda> NameError: name ''x'' is not defined >>> l(3) (1, 2, 3)

Desmontar el código revela por qué: "x" se trata como una variable global mediante "eval" y "exec".

from dis import dis print("This is `i`:") dis(i) print("This is `j`:") dis(j) print("This is `k`:") dis(k) print("This is `l`:") dis(l) print("For reference, this is `h`:") dis(h)

Salida:

This is `i`: 1 0 LOAD_GLOBAL 0 (w) 3 LOAD_GLOBAL 1 (x) 6 LOAD_FAST 0 (y) 9 BUILD_TUPLE 3 12 RETURN_VALUE This is `j`: 25 0 LOAD_GLOBAL 0 (w) 3 LOAD_DEREF 0 (x) 6 LOAD_FAST 0 (y) 9 BUILD_TUPLE 3 12 RETURN_VALUE This is `k`: 1 0 LOAD_GLOBAL 0 (w) 3 LOAD_GLOBAL 1 (x) 6 LOAD_FAST 0 (y) 9 BUILD_TUPLE 3 12 RETURN_VALUE This is `l`: 27 0 LOAD_GLOBAL 0 (w) 3 LOAD_DEREF 0 (x) 6 LOAD_FAST 0 (y) 9 BUILD_TUPLE 3 12 RETURN_VALUE For reference, this is `h`: 22 0 LOAD_NAME 0 (locals) 3 CALL_FUNCTION 0 6 STORE_FAST 1 (ls) 23 9 LOAD_CONST 1 (''def i(y): return (w, x, y)'') 12 LOAD_NAME 1 (globals) 15 CALL_FUNCTION 0 18 LOAD_FAST 1 (ls) 21 EXEC_STMT 24 22 LOAD_FAST 1 (ls) 25 LOAD_CONST 2 (''i'') 28 BINARY_SUBSCR 29 STORE_FAST 2 (i) 25 32 LOAD_CLOSURE 0 (x) 35 BUILD_TUPLE 1 38 LOAD_CONST 3 (<code object j at 0x7ffc3843c030, file "test.py", line 25>) 41 MAKE_CLOSURE 0 44 STORE_FAST 3 (j) 26 47 LOAD_NAME 2 (eval) 50 LOAD_CONST 4 (''lambda y: (w, x, y)'') 53 CALL_FUNCTION 1 56 STORE_FAST 4 (k) 27 59 LOAD_CLOSURE 0 (x) 62 BUILD_TUPLE 1 65 LOAD_CONST 5 (<code object <lambda> at 0x7ffc3843c3b0, file "test.py", line 27>) 68 MAKE_CLOSURE 0 71 STORE_FAST 5 (l) 28 74 LOAD_FAST 2 (i) 77 LOAD_FAST 3 (j) 80 LOAD_FAST 4 (k) 83 LOAD_FAST 5 (l) 86 BUILD_TUPLE 4 89 RETURN_VALUE

La pregunta

"j" y "l" de arriba tienen el comportamiento que quiero. ¿Cómo puedo obtener este comportamiento usando "eval" o "exec"?

Fracaso 1

Usar una clase en lugar de una función como envoltorio externo cambia la semántica, pero en sentido opuesto a la forma deseada. Convierte "x" en un global.

class H: x = 2 f = staticmethod(eval(''lambda y: (w, x, y)'')) H.dis(H.f) w = 1 H.f(3)

Salida:

1 0 LOAD_GLOBAL 0 (w) 3 LOAD_GLOBAL 1 (x) 6 LOAD_FAST 0 (y) 9 BUILD_TUPLE 3 12 RETURN_VALUE Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 1, in <lambda> NameError: global name ''x'' is not defined

Envolver en "método de clase" o dejarlo como un método de instancia no consolidado solo empeora las cosas.

Fracaso 2

Sustituir la "x" por trabajos de interpolación de cadenas por enteros:

def h(x): return eval(''lambda y: (w, %r, y)'' % x) k = h(2) dis(k) w = 1 k(3)

Salida:

1 0 LOAD_GLOBAL 0 (w) 3 LOAD_CONST 1 (2) 6 LOAD_FAST 0 (y) 9 BUILD_TUPLE 3 12 RETURN_VALUE (1, 2, 3)

Sin embargo, no quiero asumir que "x" se puede convertir sin pérdidas a una cadena y viceversa. El intento se rompe en los siguientes ejemplos:

k = h(lambda: "something") k = h(open(''some_file'', ''w'')) cell = ["Wrong value"] k = h(cell) cell[0] = "Right value" k(3)

Fracaso 3

Como Python está buscando una variable global, un intento obvio es pasar "x" como una variable global:

def h(x): my_globals = {''w'': w, ''x'': x} return eval(''lambda y: (w, x, y)'', my_globals) k = h(2) dis(k) w = 1 k(3)

Salida:

1 0 LOAD_GLOBAL 0 (w) 3 LOAD_GLOBAL 1 (x) 6 LOAD_FAST 0 (y) 9 BUILD_TUPLE 3 12 RETURN_VALUE (1, 2, 3)

Este intento se interrumpe porque lee el valor de "w" demasiado pronto:

w = "Wrong value" k = h(2) w = "Right value" k(3)

Éxito 1

Eventualmente encontré un enfoque que funciona, pero realmente no me gusta:

def h(x): return eval(''lambda x: lambda y: (w, x, y)'')(x) k = h(2) dis(k) w = 1 k(3)

Salida:

1 0 LOAD_GLOBAL 0 (w) 3 LOAD_DEREF 0 (x) 6 LOAD_FAST 0 (y) 9 BUILD_TUPLE 3 12 RETURN_VALUE (1, 2, 3)

En particular, esto será doloroso si no conozco la lista completa de variables locales capturadas por la cadena que estoy pasando a "eval".

¿Puedes hacerlo mejor?

Actualización 2014-12-25

Fracaso 4

Buscando más formas de crear la variable local "x", probé esto:

def h(x): ls = locals() exec(''x = x/ndef i(y): return (w, x, y)'', globals(), ls) exec(''_ = x/ndef j(y): return (w, x, y)'', globals(), ls) return ls[''i''], ls[''j''], ls[''_''], ls[''x''] i, j, check1, check2 = h(2) assert check1 == 2 assert check2 == 2 w = 1 print("This is `i`:") dis(i) print("This is `j`:") dis(j) print("i(3) = %r" % (i(3),)) print("j(3) = %r" % (j(3),))

La asignación adicional a "x" no tiene ningún efecto. Las afirmaciones verifican que "x" está en el diccionario de los locales, pero no está capturado por las lambdas. Aquí está la salida:

This is `i`: 2 0 LOAD_GLOBAL 0 (w) 3 LOAD_GLOBAL 1 (x) 6 LOAD_FAST 0 (y) 9 BUILD_TUPLE 3 12 RETURN_VALUE This is `j`: 2 0 LOAD_GLOBAL 0 (w) 3 LOAD_GLOBAL 1 (x) 6 LOAD_FAST 0 (y) 9 BUILD_TUPLE 3 12 RETURN_VALUE

Las llamadas a "i" y "j" se bloquean, y se quejan de que no hay una variable global "x".

Éxito 2

[Edición 2014-12-29: Esto solo tiene éxito en Python 3.]

Otra forma de crear una variable local es así:

def h(x): i = eval(''[lambda y: (w, x, y) for x in [x]][0]'') j = eval(''[lambda y: (w, x, y) for _ in [x]][0]'') return i, j i, j = h(2) w = 1 print("This is `i`:") dis(i) print("This is `j`:") dis(j) print("i(3) = %r" % (i(3),)) print("j(3) = %r" % (j(3),))

Extrañamente, en este caso, la asignación adicional a "x" tiene un efecto. Esto funciona, es decir, "i" es diferente de "j". Aquí está la salida:

This is `i`: 1 0 LOAD_GLOBAL 0 (w) 3 LOAD_DEREF 0 (x) 6 LOAD_FAST 0 (y) 9 BUILD_TUPLE 3 12 RETURN_VALUE This is `j`: 1 0 LOAD_GLOBAL 0 (w) 3 LOAD_GLOBAL 1 (x) 6 LOAD_FAST 0 (y) 9 BUILD_TUPLE 3 12 RETURN_VALUE i(3) = (1, 2, 3)

La llamada a "j" se bloquea, quejándose de que no hay una "x" global, pero "i" funciona como se desea y tiene el código de byte correcto.

¿Por qué funciona esto, mientras que el "Fallo 4" anterior no lo hace? ¿Cuál es la regla que determina si se puede capturar la "x" local? ¿Y cuál es la historia de este diseño? (¡Parece absurdo!)


Compartiré en mi propio entendimiento de por qué Python se comporta así.

Cómo Lambda captura las referencias

Cada vez que una variable que no está en la lista de parámetros utilizada, Python crea un cierre para ella. Por ejemplo

y def h(x): l =lambda y: (w, x,y)

Crea un cierre que captura x, puedes comprobarlo accediendo a

l.__closure__

lo que te mostrará que x es store junto con la creación de la función. Sin embargo, y NO se almacena con la función porque se define como una variable global

Definición de clase

Esto causará un error de nombre cuando se ejecute Af()

class A: c = 1 f = lambda :c+1

ya que Python buscará c en el espacio de nombres global que no tiene c definida

La razón

Python 3''s doc a exec function dijo

Si exec obtiene dos objetos separados como globales y locales , el código se ejecutará como si estuviera incrustado en una definición de clase .

que muestra por qué la lambda no es capturar variables en el espacio de nombres de los locales

Trabajo alrededor

k = eval(''lambda y: (w, x, y)'',dict(globals(),**))


Creo que quieres que tus funciones creadas hereden el entorno local de la función que las crea, pero también el entorno global real (de la función que las crea). Es por eso que no te gustan que se refieran a x como global, ¿verdad?

Lo siguiente crea una función de "envoltura" alrededor de la función deseada, todo dentro de la misma cadena ejecutiva. Los valores de los locales de la función de creación se pasan cuando llama o vuelve a llamar al contenedor , creando un nuevo cierre envuelto.

El código es sensible a las nuevas variables que se crean en el contexto local. Es un problema asegurarse de que tanto la función como los nombres de envoltura sean conocidos y tengan valores allí.

def wrap_f(code, gs, ls, wrapper_name, function_name): ls[function_name] = ls[wrapper_name] = None arglist = ",".join(ls.keys()) wrapcode = """ def {wrapper_name}({arglist}): {code} return {function_name} """.format(wrapper_name=wrapper_name, arglist=arglist, code=code, function_name=function_name) exec(wrapcode, gs, ls) wrapper = ls[wrapper_name] return wrapper, wrapper(**ls)

Entonces, para responder a la pregunta original, este código ...

def h(x): mcode = " def m(y): return (w, x, y)" # must be indented 4 spaces. mwrap, m = wrap_f(mcode, globals(), locals(), "mwrap", "m") return m w = 1 m = h(2) print m(3)

... produce esta salida:

(1, 2, 3)

Y este ejemplo muestra qué hacer cuando cambian los locales en la función de creador:

def foo(x): barleycode = """ def barley(y): print "barley''s x =", x print "barley''s y =", y """ barleywrap, barley = wrap_f(barleycode, globals(), locals(), "barleywrap", "barley") barley("this string") print x = "modified x" barley = barleywrap(**locals()) barley("new arg") print x = "re-modified x" barley("doesn''t see the re-mod.") x = "the global x" foo("outer arg")

Esto produce la salida:

barley''s x = outer arg barley''s y = this string barley''s x = modified x barley''s y = new arg barley''s x = modified x barley''s y = doesn''t see the re-mod.


No estoy seguro de haberlo hecho bien, pero lo intentaré lo mejor posible: creo que cuando ejecutas el python eval / exec no entiendo que estaba dentro de la función, realmente no sé por qué. Lo que intentaré hacer es usar una cadena de formato como esta

k = eval("lambda y: (w, {0}, y)".format(x))

Aunque no estoy seguro de si esto funciona. Además, ¿por qué necesitas usar eval y exec de esta manera?