2012-05-04 16 views
33

El siguiente código escupe 1 dos veces, espero ver 0 y luego 1Python lambda de unión a los valores locales

def pv(v) : 
    print v 


def test() : 
    value = [] 
    value.append(0) 
    value.append(1) 
    x=[] 
    for v in value : 
    x.append(lambda : pv(v)) 
    return x 

x = test() 
for xx in x: 
    xx() 

que esperaba lambdas pitón para unirse a la referencia a una variable local que hace referencia, detrás de la escena. Sin embargo, ese no parece ser el caso. He encontrado este problema en un sistema grande donde el lambda está haciendo el equivalente de C++ moderno de C++ ('boost :: bind' por ejemplo) donde en ese caso se uniría a un ptr inteligente o se copiaría una copia para el lambda.

Entonces, ¿cómo puedo vincular una variable local a una función lambda y hacer que conserve la referencia correcta cuando se usa? Estoy bastante desconcertado con el comportamiento ya que no esperaría esto de un lenguaje con un recolector de basura.

El código en cuestión se ve de la siguiente manera (l3_e es la variable que está causando el problema):

for category in cat : 
     for l2 in cat[category].entries : 
     for l3 in cat[category].entries[l2].entry["sub_entries"] : 
      l3_e = cat[category].entries[l2].entry["sub_entries"][l3] 
      url = "http://forums.heroesofnewerth.com/" + l3_e.entry["url"] 
      self.l4_processing_status[l3_e] = 0 
      l3_discovery_requests.append(Request(
      url, callback = lambda response : self.parse_l4(response,l3_e))) 
      print l3_e.entry["url"] 
    return l3_discovery_requests 
+2

He visto varias variantes de esta pregunta. alguien tiene que agregarlos todos, cambiar el título a algo memorable, y luego decirle a Internet que – Shep

+1

ah, aquí va, cerca de duplicados: [lexical-closures-in-python] (http://stackoverflow.com/q/233673/915501) – Shep

+0

Además, el primer fragmento de código ilustra perfectamente tu punto, ¿por qué pegar en este segundo trozo? – Shep

Respuesta

59

Cambio x.append(lambda : pv(v)) a x.append(lambda v=v: pv(v)).

Se espera que "python lambdas se vincule a la referencia a la que apunta una variable local, detrás de la escena", pero no es así como funciona Python. Python busca el nombre de la variable en el momento en que se llama a la función, no cuando se crea. Usar un argumento predeterminado funciona porque los argumentos predeterminados se evalúan cuando se crea la función, no cuando se la llama.

Esto no es algo especial acerca de lambdas. Considere lo siguiente:

x = "before foo defined" 
def foo(): 
    print x 
x = "after foo was defined" 
foo() 

impresiones

after foo was defined 
+0

interesante, ¿podría explicar la intuición de la mecánica, por favor? –

+1

Una alternativa más obvia es usar 'functools.partial' o usar una función de ayuda por separado para crear los cierres (' def make_closure (v): return lambda: pv (v) ', puedes ponerlo en' test'). – delnan

+0

así que mi código real debería gustarle a 'lambda par1, par2 = l3_e: self.parse_l4 (par1, par2)'? –

12

El cierre de la lambda contiene una referencia a la variable que está siendo utilizado, no su valor, por lo que si el valor de la variable posteriores cambios, el valor en el cierre también cambia . Es decir, el valor de la variable de cierre se resuelve cuando se llama a la función, no cuando se crea. (Comportamiento de Python aquí no es inusual en el mundo de la programación funcional, por lo que vale la pena.)

hay dos soluciones:

  1. utilizar un argumento predeterminado, vinculante el valor actual de la variable a un local de nombre en el momento de la definición. lambda v=v: pv(v)

  2. Utilice una doble lambda e inmediatamente llame al primero. (lambda v: lambda: pv(v))(v)

Cuestiones relacionadas