When I wrote some code, I found a interesting thing:
def test():
l = []
for i in range(10):
def f():pass
print(f)
#l.append(f)
test()
import dis
dis.dis(test)
The output is :
<function test.<locals>.f at 0x7f46c0b0d400>
<function test.<locals>.f at 0x7f46c0b0d488>
<function test.<locals>.f at 0x7f46c0b0d400>
<function test.<locals>.f at 0x7f46c0b0d488>
<function test.<locals>.f at 0x7f46c0b0d400>
<function test.<locals>.f at 0x7f46c0b0d488>
<function test.<locals>.f at 0x7f46c0b0d400>
<function test.<locals>.f at 0x7f46c0b0d488>
<function test.<locals>.f at 0x7f46c0b0d400>
<function test.<locals>.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 (<code object f at 0x7f46c0bd8420, file "ts.py", line 8>)
28 LOAD_CONST 3 ('test.<locals>.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
when
def test():
l = []
for i in range(10):
def f():pass
print(f)
l.append(f)
test()
import dis
dis.dis(test)
And the output is:
<function test.<locals>.f at 0x7ff88ffe0400>
<function test.<locals>.f at 0x7ff88ffe0488>
<function test.<locals>.f at 0x7ff88ffe0510>
<function test.<locals>.f at 0x7ff88ffe0598>
<function test.<locals>.f at 0x7ff88ffe0620>
<function test.<locals>.f at 0x7ff88ffe06a8>
<function test.<locals>.f at 0x7ff88ffe0730>
<function test.<locals>.f at 0x7ff88ffe07b8>
<function test.<locals>.f at 0x7ff88ffe0840>
<function test.<locals>.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 (<code object f at 0x7ff8900ab420, file "ts.py", line 8>)
28 LOAD_CONST 3 ('test.<locals>.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
If STORE_FAST
"cached" the f
, why in the first code snippet, the address of f
is alternant?
And in the second snippet, it has two LOAD_FAST
, and the result is normal.
Is LOAD_FAST/STORE_FAST did some unknow things?
That's happening because in each alternate iteration the older function object after the re-declaration of the current f
has no references left, so it is garbage collected and Python can re-use that memory space in next iteration. On the other hand in the second the list is referring to each function so they are never garbage collected.
This is an implementation dependent thing, CPython's garbage collection is based on reference count. On PyPy the output is different:
$ ~/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()
<function f at 0x00007f055c77d5b0>
<function f at 0x00007f055c77d628>
<function f at 0x00007f055c77d6a0>
<function f at 0x00007f055c77d718>
<function f at 0x00007f055c77d790>
<function f at 0x00007f055c77d808>
<function f at 0x00007f055c77d880>
<function f at 0x00007f055c77d8f8>
<function f at 0x00007f055c77d970>
<function f at 0x00007f055c77d9e8>