Python 中,代码放在函数中运行为什么比放在全局中运行快?

因为CPython的解释器实现细节。CPython的解释器,对局部变量是用数组存储,用下标来访问;而对全局变量是用dict来存储,用符号(symbol)来做hash访问。速度差距是杠杠的。这是因为一旦函数定义好之后,局部变量的个数就不能改了,所以可以用固定大小的容器存储;而全局名字是可以一边执行一边改变的,所以得用更动态的方式来存储。看CPython的字节码的 LOAD_FAST 与 LOAD_GLOBAL 就可以看出差异了 _对上面的描述觉得迷惑的同学,可以先参考一些背景资料:有没有内容类似于《Python源码剖析》,但内容更新过,针对新版本的Python书籍? - RednaxelaFX 的回答特别是其中用Python实现的教学用CPython字节码解释器的例子:A Python Interpreter Written in Python(其代码在 byterun/pyvm2.py at master · nedbat/byterun · GitHub)这个用Python实现的解释器其中就有这里提到的读写局部变量用的 LOAD_FAST / STORE_FAST 指令的实现——但为了简单起见,它实现这两条字节码指令是用 dict 来存储数据的,而不是像真正的CPython那样用数组来存储。其实稍微改改这个Python代码就可以让它在这方面更接近CPython的样子了。===========================================评论区有同学提到 locals(),这在正常Python里是改变不了实际局部变量的状态的喔:$ python
Python 2.7.5 (default, Mar 9 2014, 22:15:05)
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.0.68)] on darwin
Type “help”, “copyright”, “credits” or “license” for more information.

def foo():
… a = 1
… b = 2
… locals()[‘a’] = 42
… print(a, b)

foo()
(1, 2)

import dis
dis.dis(foo)
2 0 LOAD_CONST 1 (1)
3 STORE_FAST 0 (a)

3 6 LOAD_CONST 2 (2)
9 STORE_FAST 1 (b)

4 12 LOAD_CONST 3 (42)
15 LOAD_GLOBAL 0 (locals)
18 CALL_FUNCTION 0
21 LOAD_CONST 4 (‘a’)
24 STORE_SUBSCR

5 25 LOAD_FAST 0 (a)
28 LOAD_FAST 1 (b)
31 BUILD_TUPLE 2
34 PRINT_ITEM
35 PRINT_NEWLINE
36 LOAD_CONST 0 (None)
39 RETURN_VALUE
然后也有提到exec / eval的:>>> def bar():
… a = 1
… b = 2
… exec(‘c = 3’)
… print(a, b, c)
… print(locals())

bar()
(1, 2, 3)
{‘a’: 1, ‘c’: 3, ‘b’: 2}

dis.dis(bar)
2 0 LOAD_CONST 1 (1)
3 STORE_FAST 0 (a)

3 6 LOAD_CONST 2 (2)
9 STORE_FAST 1 (b)

4 12 LOAD_CONST 3 (‘c = 3’)
15 LOAD_CONST 0 (None)
18 DUP_TOP
19 EXEC_STMT

5 20 LOAD_FAST 0 (a)
23 LOAD_FAST 1 (b)
26 LOAD_NAME 0 ©
29 BUILD_TUPLE 3
32 PRINT_ITEM
33 PRINT_NEWLINE

6 34 LOAD_NAME 1 (locals)
37 CALL_FUNCTION 0
40 PRINT_ITEM
41 PRINT_NEWLINE
42 LOAD_CONST 0 (None)
45 RETURN_VALUE
注意这里“局部变量”c是在一个exec语句(Python 3的话是exec()函数)里动态定义的,而在exec语句后对c的使用就用的是LOAD_NAME字节码而不是普通局部变量用的LOAD_FAST——这说明了动态定义的局部变量与普通局部变量的差异,而前面说“局部变量的个数不会改变”不包括这种动态定义的情况。作者:RednaxelaFX

因为CPython的解释器实现细节。CPython的解释器,对局部变量是用数组存储,用下标来访问;而对全局变量是用dict来存储,用符号(symbol)来做hash访问。速度差距是杠杠的。这是因为一旦函数定义好之后,局部变量的个数就不能改了,所以可以用固定大小的容器存储;而全局名字是可以一边执行一边改变的,所以得用更动态的方式来存储。看CPython的字节码的 LOAD_FAST 与 LOAD_GLOBAL 就可以看出差异了 _对上面的描述觉得迷惑的同学,可以先参考一些背景资料:有没有内容类似于《Python源码剖析》,但内容更新过,针对新版本的Python书籍? - RednaxelaFX 的回答特别是其中用Python实现的教学用CPython字节码解释器的例子:A Python Interpreter Written in Python(其代码在 byterun/pyvm2.py at master · nedbat/byterun · GitHub)这个用Python实现的解释器其中就有这里提到的读写局部变量用的 LOAD_FAST / STORE_FAST 指令的实现——但为了简单起见,它实现这两条字节码指令是用 dict 来存储数据的,而不是像真正的CPython那样用数组来存储。其实稍微改改这个Python代码就可以让它在这方面更接近CPython的样子了。===========================================评论区有同学提到 locals(),这在正常Python里是改变不了实际局部变量的状态的喔:$ python
Python 2.7.5 (default, Mar 9 2014, 22:15:05)
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.0.68)] on darwin
Type “help”, “copyright”, “credits” or “license” for more information.

def foo():
… a = 1
… b = 2
… locals()[‘a’] = 42
… print(a, b)

foo()
(1, 2)

import dis
dis.dis(foo)
2 0 LOAD_CONST 1 (1)
3 STORE_FAST 0 (a)

3 6 LOAD_CONST 2 (2)
9 STORE_FAST 1 (b)

4 12 LOAD_CONST 3 (42)
15 LOAD_GLOBAL 0 (locals)
18 CALL_FUNCTION 0
21 LOAD_CONST 4 (‘a’)
24 STORE_SUBSCR

5 25 LOAD_FAST 0 (a)
28 LOAD_FAST 1 (b)
31 BUILD_TUPLE 2
34 PRINT_ITEM
35 PRINT_NEWLINE
36 LOAD_CONST 0 (None)
39 RETURN_VALUE
然后也有提到exec / eval的:>>> def bar():
… a = 1
… b = 2
… exec(‘c = 3’)
… print(a, b, c)
… print(locals())

bar()
(1, 2, 3)
{‘a’: 1, ‘c’: 3, ‘b’: 2}

dis.dis(bar)
2 0 LOAD_CONST 1 (1)
3 STORE_FAST 0 (a)

3 6 LOAD_CONST 2 (2)
9 STORE_FAST 1 (b)

4 12 LOAD_CONST 3 (‘c = 3’)
15 LOAD_CONST 0 (None)
18 DUP_TOP
19 EXEC_STMT

5 20 LOAD_FAST 0 (a)
23 LOAD_FAST 1 (b)
26 LOAD_NAME 0 ©
29 BUILD_TUPLE 3
32 PRINT_ITEM
33 PRINT_NEWLINE

6 34 LOAD_NAME 1 (locals)
37 CALL_FUNCTION 0
40 PRINT_ITEM
41 PRINT_NEWLINE
42 LOAD_CONST 0 (None)
45 RETURN_VALUE
注意这里“局部变量”c是在一个exec语句(Python 3的话是exec()函数)里动态定义的,而在exec语句后对c的使用就用的是LOAD_NAME字节码而不是普通局部变量用的LOAD_FAST——这说明了动态定义的局部变量与普通局部变量的差异,而前面说“局部变量的个数不会改变”不包括这种动态定义的情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值