python的变量作用域
2013-12-09 15:29
351 查看
1. 不在函数体内的变量或者在 if __name__=='__main__'中的变量,都是全局变量,注意访问这些全局变量的速度是比较慢的,因为这些全局变量放在一个全局的表中,需要查找
2. 在函数体内,如果不没有对变量的赋值操作,默认这个变量是全局变量,就是要从全局的表中查找
3. 在函数体内,如果有对变量的赋值操作,则这边变量是局部变量;如果想在函数体内修改全局变量,只需在函数体内声明global即可
http://hyry.dip.jp/tech/book/page/python/variable_scope_global.html
在函数内部可以访问全局变量,例如:
在函数f1()创建时,无论表示全局对象字典的func_globals属性中是否存在”x”,在函数内部变量x都会当作全局变量处理。只要在f1()运行时能找到全局变量x即可。通过代码对象的co_names属性可以获得代码中访问的全局变量名,而co_varnames属性可以获得代码中使用的局域变量名。
co_names并不是单纯用来保存全局变量名,后面我们还会看到其它的用法。
如果函数中存在对变量x赋值的语句,那么它将被当作函数的局域变量:
通过查看字节码,可以发现其中的区别:
在f1()中使用LOAD_GLOBAL命令载入变量x的值,而在f2()中则使用LOAD_FAST和STORE_FAST命令存取变量的值。查看这几个命令的帮助可知:
LOAD_GLOBAL在代码对象的co_names属性中寻找变量名。
LOAD_FAST和STORE_FAST在代码对象的co_varnames属性中寻找变量名。
请注意这些命令的参数都为整数,变量名是通过将参数作为下标从对应的变量名元组中获得的。所以:“LOAD_GLOBAL 0”相当于载入f1.func_globals[f1.func_code.co_names[0]]对象。
对于局域变量,Python做了尽可能的优化处理,因此它使用LOAD_FAST和STORE_FAST对局域变量进行存取,它们可以通过其参数直接存取一个用来保存局域变量的C语言数组。
如果我们希望函数修改全局变量,那么需要在函数内使用global关键字进行声明。
可以看到当使用global关键字将变量x声明为全局变量之后,字节码中就改为使用STORE_GLOBAL和LOAD_GLOBAL命令了。
由上述的结果我们可以做如下总结:
在函数内未被赋值而直接使用的变量为全局变量。
在函数内用global声明的均为全局变量,否则若存在赋值语句,则被赋值的变量为局域变量。
全局变量的规则虽然简单,但是稍有不慎就会出现一些意想不到的错误。为了防范于未然,读者要牢记Python是先编译成字节码之后再运行的,在编译时变量是全局的还是局域的就已经决定了。然而编译器的工作方式和我们通常理解程序的方式存在区别,因此会出现一些难以发觉的错误。
请避免编写如下容易引起理解混淆的程序。
上面的程序的输出如下:
这是因为无论global关键字在何处,它的声明对整个函数体都是有效的。当编译器发现函数体中存在“global x”语句,它就把变量x当作全局变量处理。这和我们对代码的直观理解不同,因此请避免编写这种代码。
下面再看一个错误更加隐蔽的例子:
由于f2()会抛出异常,因此我们在调用它时用try/except捕捉了错误。程序的输出为:
和global关键字一样,无论对变量的赋值出现在何处,这个变量都会被当作局域变量。甚至它出现在明显不可能被执行的位置。在f2()中,❷由于存在语句“len = 10”,因此len被当作局域变量,在字节码中会使用LOAD_FAST命令载入变量len。❶而运行“len(x)”载入变量len所引用的对象时,LOAD_FAST在局域变量字典中找不到,于是报错。
虽然❷是真正需要修改的语句,Python却会在❶处报错。特别是当函数体较长时,这个错误很难发觉。因此使用内置函数或模块名作为变量名是需要绝对避免的,当你需要用len做为变量名时,可以考虑多添加一个下划线:len_。
通过上节的叙述我们知道Python编译器通过global关键字和变量赋值语句决定某个变量是全局还是局域的。然而由于Python是如此的动态,我们可以在函数内部用exec命令动态地将字符串当作程序执行。对于Python编译器来说,这个表示程序的字符串是无法在程序的编译期进行分析的。因此Python必须对其进行额外的处理。
先看一个最简单的例子:
程序的输出为:
显然Python将变量a当作局域变量处理。但是它应该不能解析exec语句到底执行了什么程序,从而判断变量a是一个局域变量。让我们查看一下编译之后的字节码:
可以看到字节码中使用LOAD_NAME命令载入变量a的值,而不是LOAD_GLOBAL或LOAD_FAST。LOAD_NAME命令从代码对象的co_names属性读取变量名,然后依次从局域变量的字典以及全局变量的字典寻找对应的值。
由于exec命令在局域变量字典中创建了变量a,因此LOAD_NAME先找到了它。
和前面所介绍的global关键字以及赋值语句一样,只要函数体中出现exec语句,那么被之前判断为全局的变量都采用LOAD_NAME命令载入。这样,当exec语句动态地创建了局域变量时,能优先载入局域变量的值,当局域变量不存在时,再载入全局变量。
在函数中被赋值的变量,仍然会被当作局域变量,而被global声明的变量则仍然被当作全局变量。
因此对于上面的程序,变量a使用LOAD_FAST载入,变量b使用LOAD_GLOBAL载入,而变量c使用LOAD_NAME载入。请读者自行通过查看字节码验证。
请读者思考下面的程序的输出,并通过字节码进行说明。
由于多了一次查找,因此LOAD_NAME比LOAD_GLOBAL的执行效率略低。下面的程序比较二者的区别。
variable_scope_exec_time.py
当在函数内存在exec语句时,会影响全局变量的查找速度
程序的输出为:
比较全局变量和局部变量的速度
http://coolshell.cn/articles/7886.html
考虑下面的代码,一个在函数体内,一个是全局的代码。
函数内的代码执行效率为 1.8s
函数体外的代码执行效率为 4.5s
不用太纠结时间,只是一个示例,我们可以看到效率查得很多。为什么会这样呢?我们使用
Main函数反汇编
全局代码
我们可以看到,差别就是
2. 在函数体内,如果不没有对变量的赋值操作,默认这个变量是全局变量,就是要从全局的表中查找
3. 在函数体内,如果有对变量的赋值操作,则这边变量是局部变量;如果想在函数体内修改全局变量,只需在函数体内声明global即可
http://hyry.dip.jp/tech/book/page/python/variable_scope_global.html
访问全局变量
在函数内部可以访问全局变量,例如:def f1(): print x
在函数f1()创建时,无论表示全局对象字典的func_globals属性中是否存在”x”,在函数内部变量x都会当作全局变量处理。只要在f1()运行时能找到全局变量x即可。通过代码对象的co_names属性可以获得代码中访问的全局变量名,而co_varnames属性可以获得代码中使用的局域变量名。
co_names并不是单纯用来保存全局变量名,后面我们还会看到其它的用法。
>>> run variable_scope02.py >>> f1.func_code.co_names # 全局变量名 ('x',) >>> f1.func_code.co_varnames # 局域变量名 ()
如果函数中存在对变量x赋值的语句,那么它将被当作函数的局域变量:
def f2(): x = 10 print x
>>> f2.func_code.co_names () >>> f2.func_code.co_varnames ('x',)
通过查看字节码,可以发现其中的区别:
>>> dis.dis(f1) 4 0 LOAD_GLOBAL 0 (x) 3 PRINT_ITEM 4 PRINT_NEWLINE 5 LOAD_CONST 0 (None) 8 RETURN_VALUE >>> dis.dis(f2) 10 0 LOAD_CONST 1 (10) 3 STORE_FAST 0 (x) 11 6 LOAD_FAST 0 (x) 9 PRINT_ITEM 10 PRINT_NEWLINE 11 LOAD_CONST 0 (None) 14 RETURN_VALUE
在f1()中使用LOAD_GLOBAL命令载入变量x的值,而在f2()中则使用LOAD_FAST和STORE_FAST命令存取变量的值。查看这几个命令的帮助可知:
LOAD_GLOBAL在代码对象的co_names属性中寻找变量名。
LOAD_FAST和STORE_FAST在代码对象的co_varnames属性中寻找变量名。
请注意这些命令的参数都为整数,变量名是通过将参数作为下标从对应的变量名元组中获得的。所以:“LOAD_GLOBAL 0”相当于载入f1.func_globals[f1.func_code.co_names[0]]对象。
对于局域变量,Python做了尽可能的优化处理,因此它使用LOAD_FAST和STORE_FAST对局域变量进行存取,它们可以通过其参数直接存取一个用来保存局域变量的C语言数组。
如果我们希望函数修改全局变量,那么需要在函数内使用global关键字进行声明。
def f3(): global x x = 20 print x
>>> dis.dis(f3) 17 0 LOAD_CONST 1 (20) 3 STORE_GLOBAL 0 (x) 18 6 LOAD_GLOBAL 0 (x) 9 PRINT_ITEM 10 PRINT_NEWLINE 11 LOAD_CONST 0 (None) 14 RETURN_VALUE
可以看到当使用global关键字将变量x声明为全局变量之后,字节码中就改为使用STORE_GLOBAL和LOAD_GLOBAL命令了。
由上述的结果我们可以做如下总结:
在函数内未被赋值而直接使用的变量为全局变量。
在函数内用global声明的均为全局变量,否则若存在赋值语句,则被赋值的变量为局域变量。
全局变量不简单
全局变量的规则虽然简单,但是稍有不慎就会出现一些意想不到的错误。为了防范于未然,读者要牢记Python是先编译成字节码之后再运行的,在编译时变量是全局的还是局域的就已经决定了。然而编译器的工作方式和我们通常理解程序的方式存在区别,因此会出现一些难以发觉的错误。请避免编写如下容易引起理解混淆的程序。
x = 0 def f1(flag): if flag: global x x = 10 x = 20 print x f1(False) print x
上面的程序的输出如下:
20 20
这是因为无论global关键字在何处,它的声明对整个函数体都是有效的。当编译器发现函数体中存在“global x”语句,它就把变量x当作全局变量处理。这和我们对代码的直观理解不同,因此请避免编写这种代码。
下面再看一个错误更加隐蔽的例子:
def f2(x): print len(x) ❶ if False: len = 10 ❷ print len try: f2([1,2,3]) except Exception as ex: print ex
由于f2()会抛出异常,因此我们在调用它时用try/except捕捉了错误。程序的输出为:
local variable 'len' referenced before assignment
和global关键字一样,无论对变量的赋值出现在何处,这个变量都会被当作局域变量。甚至它出现在明显不可能被执行的位置。在f2()中,❷由于存在语句“len = 10”,因此len被当作局域变量,在字节码中会使用LOAD_FAST命令载入变量len。❶而运行“len(x)”载入变量len所引用的对象时,LOAD_FAST在局域变量字典中找不到,于是报错。
虽然❷是真正需要修改的语句,Python却会在❶处报错。特别是当函数体较长时,这个错误很难发觉。因此使用内置函数或模块名作为变量名是需要绝对避免的,当你需要用len做为变量名时,可以考虑多添加一个下划线:len_。
exec语句的影响
通过上节的叙述我们知道Python编译器通过global关键字和变量赋值语句决定某个变量是全局还是局域的。然而由于Python是如此的动态,我们可以在函数内部用exec命令动态地将字符串当作程序执行。对于Python编译器来说,这个表示程序的字符串是无法在程序的编译期进行分析的。因此Python必须对其进行额外的处理。先看一个最简单的例子:
a = 0 def f1(): exec("a=100") print a f1() print a
程序的输出为:
100 0
显然Python将变量a当作局域变量处理。但是它应该不能解析exec语句到底执行了什么程序,从而判断变量a是一个局域变量。让我们查看一下编译之后的字节码:
>>> dis.dis(f1) 7 0 LOAD_CONST 1 ('a=100') 3 LOAD_CONST 0 (None) 6 DUP_TOP 7 EXEC_STMT 8 8 LOAD_NAME 0 (a) <--- 使用LOAD_NAME载入变量a的值 11 PRINT_ITEM 12 PRINT_NEWLINE 13 LOAD_CONST 0 (None) 16 RETURN_VALUE
可以看到字节码中使用LOAD_NAME命令载入变量a的值,而不是LOAD_GLOBAL或LOAD_FAST。LOAD_NAME命令从代码对象的co_names属性读取变量名,然后依次从局域变量的字典以及全局变量的字典寻找对应的值。
>>> f1.func_code.co_names ('a',)
由于exec命令在局域变量字典中创建了变量a,因此LOAD_NAME先找到了它。
和前面所介绍的global关键字以及赋值语句一样,只要函数体中出现exec语句,那么被之前判断为全局的变量都采用LOAD_NAME命令载入。这样,当exec语句动态地创建了局域变量时,能优先载入局域变量的值,当局域变量不存在时,再载入全局变量。
在函数中被赋值的变量,仍然会被当作局域变量,而被global声明的变量则仍然被当作全局变量。
def f3(): exec("") a = 1 global b print a,b,c
因此对于上面的程序,变量a使用LOAD_FAST载入,变量b使用LOAD_GLOBAL载入,而变量c使用LOAD_NAME载入。请读者自行通过查看字节码验证。
请读者思考下面的程序的输出,并通过字节码进行说明。
a = 0 def f2(): print a exec("a=10") print a f2() print a
由于多了一次查找,因此LOAD_NAME比LOAD_GLOBAL的执行效率略低。下面的程序比较二者的区别。
variable_scope_exec_time.py
当在函数内存在exec语句时,会影响全局变量的查找速度
import time a = 1 def f1(): exec "" start = time.clock() sum_ = 0 for i in xrange(100000): sum_ += a print "with exec:", time.clock() - start def f2(): start = time.clock() sum_ = 0 for i in xrange(100000): sum_ += a print "without exec:", time.clock() - start f1() f2()
程序的输出为:
with exec: 0.0217457921526 without exec: 0.0197440029085
比较全局变量和局部变量的速度
http://coolshell.cn/articles/7886.html
考虑下面的代码,一个在函数体内,一个是全局的代码。
函数内的代码执行效率为 1.8s
dismodule 反汇编函数体内的bytecode 代码,使用
compilebuiltin 反汇编全局bytecode,我们可以看到下面的反汇编(注意我高亮的地方)
STORE_FAST和
STORE_NAME,前者比后者快很多。所以,在全局代码中,变量i成了一个全局变量,而函数中的i是放在本地变量表中,所以在全局变量表中查找变量就慢很多。如果你在main函数中声明global i 那么效率也就下来了。原因是,本地变量是存在一个数组中(直到),用一个整型常量去访问,而全局变量存在一个dictionary中,查询很慢。
相关文章推荐
- 关于python的变量作用域
- Python学习之变量的作用域
- python 学习笔记-变量作用域
- python 函数 本地变量 函数的命名空间 变量的作用域
- Python 的变量作用域和 LEGB 原则
- python变量作用域
- Python 变量作用域 —— 命名空间与 LEGB 规则
- Python 变量作用域
- Python (七)变量作用域
- python和ruby变量作用域对比
- 【Python】变量作用域
- python变量作用域
- python变量作用域
- python 中模块(model)概念的引入 以及 函数变量的作用域
- Python3 变量的作用域
- php和python 中变量作用域的解析
- python变量作用域
- javascript和python函数中变量作用域的区别
- python 变量作用域
- python-变量的作用域