Python: LOAD_FAST vs. LOAD_DEREF con adición in situ
El viernes pasado fui a una entrevista de trabajo y tuve que responder la siguiente pregunta: ¿por qué este código genera una excepción?UnboundLocalError: local variable 'var' referenced before assignment
en la línea que contienevar += 1
)?
def outer():
var = 1
def inner():
var += 1
return var
return inner
No pude dar una respuesta adecuada; este hecho realmente me molestó, y cuando llegué a casa intenté con todas mis fuerzas encontrar una respuesta adecuada. Bueno yotener Encontré la respuesta, pero ahora hay algo más que me confunde.
Tengo que decir de antemano que mi pregunta es más sobre las decisiones tomadas al diseñar el lenguaje, no sobre cómo funciona.
Entonces, considere este código. La función interna es un cierre de pitón, yvar
no es local paraouter
- se almacena en una celda (y luego se recupera de una celda):
def outer():
var = 1
def inner():
return var
return inner
El desmontaje se ve así:
0 LOAD_CONST 1 (1)
3 STORE_DEREF 0 (var) # not STORE_FAST
6 LOAD_CLOSURE 0 (var)
9 BUILD_TUPLE 1
12 LOAD_CONST 2 (<code object inner at 0x10796c810)
15 LOAD_CONST 3 ('outer.<locals>.inner')
18 MAKE_CLOSURE 0
21 STORE_FAST 0 (inner)
24 LOAD_FAST 0 (inner)
27 RETURN_VALUE
recursing into <code object inner at 0x10796c810:
0 LOAD_DEREF 0 (var) # same thing
3 RETURN_VALUE
Esto cambia cuando intentamos vincular algo más avar
dentro de la función interna:
def outer():
var = 1
def inner():
var = 2
return var
return inner
Una vez más el desmontaje:
0 LOAD_CONST 1 (1)
3 STORE_FAST 0 (var) # this one changed
6 LOAD_CONST 2 (<code object inner at 0x1084a1810)
9 LOAD_CONST 3 ('outer.<locals>.inner')
12 MAKE_FUNCTION 0 # AND not MAKE_CLOSURE
15 STORE_FAST 1 (inner)
18 LOAD_FAST 1 (inner)
21 RETURN_VALUE
recursing into <code object inner at 0x1084a1810:
0 LOAD_CONST 1 (2)
3 STORE_FAST 0 (var) # 'var' is supposed to be local
6 LOAD_FAST 0 (var)
9 RETURN_VALUE
Almacenamosvar
localmente, que cumple con lo que se dice en la documentación:las asignaciones a los nombres siempre van al ámbito más interno.
Ahora, cuando intentamos hacer un incrementovar += 1
, un desagradableLOAD_FAST
aparece, que trata de obtenervar
deinner
Alcance local:
14 LOAD_FAST 0 (var)
17 LOAD_CONST 2 (2)
20 INPLACE_ADD
21 STORE_FAST 0 (var)
Y, por supuesto, tenemos un error. Ahora,esto es lo que no entiendo: por qué no podemos recuperarvar
con unLOAD_DEREF
y LUEGO guárdelo adentroinner
alcance de un conSTORE_FAST
? Quiero decir, esto parece estar bien. con la tarea de "alcance más interno", y al mismo tiempo es algo más intuitivamente deseable. Al menos el+=
el código haría lo que queremos que haga, y no puedo pensar en una situación en la que el enfoque descrito pueda estropear algo.
¿Puedes? Siento que me falta algo aquí.