Desconcertado con LOAD_FAST / STORE_FAST de python

Cuando escribí un código, encontré algo interesante:

def test(): l = [] for i in range(10): def f():pass print(f) #l.append(f) test() import dis dis.dis(test) 

La salida es:

 <function test..f at 0x7f46c0b0d400> <function test..f at 0x7f46c0b0d488> <function test..f at 0x7f46c0b0d400> <function test..f at 0x7f46c0b0d488> <function test..f at 0x7f46c0b0d400> <function test..f at 0x7f46c0b0d488> <function test..f at 0x7f46c0b0d400> <function test..f at 0x7f46c0b0d488> <function test..f at 0x7f46c0b0d400> <function test..f at 0x7f46c0b0d488> 6 0 BUILD_LIST 0 3 STORE_FAST 0 (l) 7 6 SETUP_LOOP 42 (to 51) 9 LOAD_GLOBAL 0 (range) 12 LOAD_CONST 1 (10) 15 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 18 GET_ITER >> 19 FOR_ITER 28 (to 50) 22 STORE_FAST 1 (i) 8 25 LOAD_CONST 2 () 28 LOAD_CONST 3 ('test..f') 31 MAKE_FUNCTION 0 34 STORE_FAST 2 (f) 9 37 LOAD_GLOBAL 1 (print) 40 LOAD_FAST 2 (f) 43 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 46 POP_TOP 47 JUMP_ABSOLUTE 19 >> 50 POP_BLOCK >> 51 LOAD_CONST 0 (None) 54 RETURN_VALUE 

cuando

 def test(): l = [] for i in range(10): def f():pass print(f) l.append(f) test() import dis dis.dis(test) 

Y la salida es:

 <function test..f at 0x7ff88ffe0400> <function test..f at 0x7ff88ffe0488> <function test..f at 0x7ff88ffe0510> <function test..f at 0x7ff88ffe0598> <function test..f at 0x7ff88ffe0620> <function test..f at 0x7ff88ffe06a8> <function test..f at 0x7ff88ffe0730> <function test..f at 0x7ff88ffe07b8> <function test..f at 0x7ff88ffe0840> <function test..f at 0x7ff88ffe08c8> 6 0 BUILD_LIST 0 3 STORE_FAST 0 (l) 7 6 SETUP_LOOP 55 (to 64) 9 LOAD_GLOBAL 0 (range) 12 LOAD_CONST 1 (10) 15 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 18 GET_ITER >> 19 FOR_ITER 41 (to 63) 22 STORE_FAST 1 (i) 8 25 LOAD_CONST 2 () 28 LOAD_CONST 3 ('test..f') 31 MAKE_FUNCTION 0 34 STORE_FAST 2 (f) 9 37 LOAD_GLOBAL 1 (print) 40 LOAD_FAST 2 (f) 43 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 46 POP_TOP 10 47 LOAD_FAST 0 (l) 50 LOAD_ATTR 2 (append) 53 LOAD_FAST 2 (f) 56 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 59 POP_TOP 60 JUMP_ABSOLUTE 19 >> 63 POP_BLOCK >> 64 LOAD_CONST 0 (None) 67 RETURN_VALUE 

Si STORE_FAST " STORE_FAST caché" la f , ¿por qué en el primer fragmento de código, la dirección de f es alternante?

Y en el segundo fragmento, tiene dos LOAD_FAST , y el resultado es normal.

¿LOAD_FAST / STORE_FAST hizo algunas cosas desconocidas?

Esto sucede porque en cada iteración alternativa, el objeto de función anterior después de la nueva statement de la f actual no tiene referencias, por lo que se recolecta basura y Python puede reutilizar ese espacio de memoria en la siguiente iteración. Por otro lado, en el segundo, la lista se refiere a cada función para que nunca se recojan de basura.

Esto depende de la implementación, la recolección de basura de CPython se basa en el recuento de referencias. En PyPy la salida es diferente:

 $ ~/pypy-2.4.0-linux64/bin# ./pypy Python 2.7.8 (f5dcc2477b97, Sep 18 2014, 11:33:30) [PyPy 2.4.0 with GCC 4.6.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>>> def test(): .... for i in range(10): .... def f(): pass .... print f .... >>>> test()