对 python 的 LOAD_FAST/STORE_FAST 感到困惑
Puzzled with LOAD_FAST/STORE_FAST of python
在写代码的时候,发现了一个有趣的事情:
def test():
l = []
for i in range(10):
def f():pass
print(f)
#l.append(f)
test()
import dis
dis.dis(test)
输出为:
<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
当
def test():
l = []
for i in range(10):
def f():pass
print(f)
l.append(f)
test()
import dis
dis.dis(test)
输出为:
<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
如果STORE_FAST
"cached"和f
,为什么在第一个代码片段中,f
的地址是交替的?
而在第二个片段中,它有两个 LOAD_FAST
,结果是正常的。
是不是LOAD_FAST/STORE_FAST做了一些不为人知的事情?
发生这种情况是因为在每次交替迭代中,当前 f
的重新声明后的旧函数对象没有留下任何引用,因此它被垃圾收集并且 Python 可以重新使用它内存 space 在下一次迭代中。另一方面,在第二个列表中,列表引用了每个函数,因此它们永远不会被垃圾收集。
这是一个依赖于实现的东西,CPython 的垃圾回收是基于引用计数的。在 PyPy 上输出不同:
$ ~/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>
在写代码的时候,发现了一个有趣的事情:
def test():
l = []
for i in range(10):
def f():pass
print(f)
#l.append(f)
test()
import dis
dis.dis(test)
输出为:
<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
当
def test():
l = []
for i in range(10):
def f():pass
print(f)
l.append(f)
test()
import dis
dis.dis(test)
输出为:
<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
如果STORE_FAST
"cached"和f
,为什么在第一个代码片段中,f
的地址是交替的?
而在第二个片段中,它有两个 LOAD_FAST
,结果是正常的。
是不是LOAD_FAST/STORE_FAST做了一些不为人知的事情?
发生这种情况是因为在每次交替迭代中,当前 f
的重新声明后的旧函数对象没有留下任何引用,因此它被垃圾收集并且 Python 可以重新使用它内存 space 在下一次迭代中。另一方面,在第二个列表中,列表引用了每个函数,因此它们永远不会被垃圾收集。
这是一个依赖于实现的东西,CPython 的垃圾回收是基于引用计数的。在 PyPy 上输出不同:
$ ~/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>