1. 不在函数体内的变量或者在 if __name__=='__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 = 10print 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 xx = 20print 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 xx = 10x = 20print xf1(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 = 1global bprint a,b,c

因此对于上面的程序,变量a使用LOAD_FAST载入,变量b使用LOAD_GLOBAL载入,而变量c使用LOAD_NAME载入。请读者自行通过查看字节码验证。

请读者思考下面的程序的输出,并通过字节码进行说明。
a = 0
def f2():print aexec("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_ = 0for i in xrange(100000):sum_ += aprint "with exec:", time.clock() - startdef f2():start = time.clock()sum_ = 0for i in xrange(100000):sum_ += aprint "without exec:", time.clock() - startf1()
f2()

程序的输出为:

with exec: 0.0217457921526
without exec: 0.0197440029085

比较全局变量和局部变量的速度

http://coolshell.cn/articles/7886.html

考虑下面的代码,一个在函数体内,一个是全局的代码。

函数内的代码执行效率为 1.8s

1
2
3
4
def main():
    for i in xrange(10**8):
        pass
main()

函数体外的代码执行效率为 4.5s

1
2
for i in xrange(10**8):
    pass

不用太纠结时间,只是一个示例,我们可以看到效率查得很多。为什么会这样呢?我们使用 dis module 反汇编函数体内的bytecode 代码,使用 compile builtin 反汇编全局bytecode,我们可以看到下面的反汇编(注意我高亮的地方)

Main函数反汇编
1
2
3
13 FOR_ITER                 6 (to 22)
16 STORE_FAST               1 (i)
19 JUMP_ABSOLUTE           13

全局代码
1
2
3
13 FOR_ITER                 6 (to 22)
16 STORE_NAME               1 (i)
19 JUMP_ABSOLUTE           13

我们可以看到,差别就是 STORE_FAST 和 STORE_NAME,前者比后者快很多。所以,在全局代码中,变量i成了一个全局变量,而函数中的i是放在本地变量表中,所以在全局变量表中查找变量就慢很多。如果你在main函数中声明global i 那么效率也就下来了。原因是,本地变量是存在一个数组中(直到),用一个整型常量去访问,而全局变量存在一个dictionary中,查询很慢。

python的变量作用域相关推荐

  1. python中变量作用域

    python中变量作用域采取以下规则: 1.python能够改变变量作用域的代码段是def.class.lamda. 2.if/elif/else.try/except/finally.for/whi ...

  2. Python中变量作用域问题

    我们经常听说Python函数访问局部变量.全局变量:在定义装饰器的时候,还会使用自由变量.这些不同的变量是如何赋值.初始化.查找及修改的呢?各自的作用细则又是什么样的呢?本篇尝试解答这个问题. Pyt ...

  3. python legb_Python变量作用域LEGB用法解析

    这篇文章主要介绍了Python变量作用域LEGB用法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 闭包就是, 函数内部嵌套函数. 而 装饰器只 ...

  4. Python 的变量作用域和 LEGB 原则

    在 Python 程序中创建.改变或查找变量名时,都是在一个保存变量名的地方进行中,那个地方我们称之为命名空间.作用域这个术语也称之为命名空间. 具体地说,在代码中变量名被赋值(Python 中变量声 ...

  5. python函数变量的作用域_学不会的Python函数——变量作用域

    1. LEGB函数 Python中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的.我们先来看一段代码. 上述代码有两个变量a,当在test函数中输出变量a的值是,为什么 ...

  6. Python基础-变量作用域

    1.函数作用域介绍 函数作用域 Python中函数作用域分为4种情况: L:local,局部作用域,即函数中定义的变量: E:enclosing,嵌套的父级函数的局部作用域,即包含此函数的上级函数的局 ...

  7. Python之变量作用域

    文章目录 一 变量作用域 1. Local(局部变量) 2. Enclosed(嵌套) 3. Global(全局) 4. Built-in(内置) 二 变量使用规则 三 变量的修改 1. global ...

  8. python sizeof_python 变量作用域 v.__sizeof__() python 深复制 一切皆对象 尽量减少内存消耗 赋值语句的原理...

    CPython implementation detail: This is the address of the object in memory. copy - Shallow and deep ...

  9. Python变量作用域的规则以及如何搜索内置作用域

    喜欢编程,热爱分享,希望能结交更多志同道合的朋友,一起在学习Python的道路上走得更远!有不懂的问题可以私聊我哦! **作用域:**是指变量的生效范围,例如本地变量.全局变量描述的就是不同的生效范围 ...

最新文章

  1. python函数的唯一标识_python基础教程Python通用唯一标识符uuid模块使用案例
  2. java is number_数据类型----Number
  3. 三年经验前端社招——有赞
  4. 【渝粤题库】广东开放大学 市场营销 形成性考核
  5. 基于Android的智能家居手持终端系统开发(毕设开题报告)修改版
  6. 【TensorFlow】TensorFlow函数精讲之tf.nn.conv2d()
  7. python网络编程学习笔记(4):域名系统
  8. 日志框架实现数据采集分析和报警
  9. 深圳信息职业技术学校 计算机辅助设计和制造,大学生职业生涯规划书样稿.doc...
  10. Python科学计算
  11. Firemonkey使用Android原生控件一些注意事项
  12. 徐小湛概率论与数理统计课件_考研数学 徐小湛教授线性代数90讲
  13. 微信emoji表情web显示
  14. Android 获取文件后缀名
  15. PROFINET转CAN网关监测CAN设备在线设置文档
  16. android 渠道 代码重复,Android Studio多渠道打包(示例代码)
  17. eXo Platform 3.0访谈
  18. 解决pytorch当中RuntimeError: expected scalar type Double but found Float的问题
  19. WebGL入门(三十九)-透明与不透明物体共存,绘制透明面和不透明面的立方体
  20. 智能车|直流电机、编码器与驱动器---减速器

热门文章

  1. 期末考试前的预习,科目:化工设备与反应器(3)
  2. 十三、开多线程,咱们一起来斗图
  3. linux配置usb主从_一种Linux下USB设备主从切换的实现
  4. 未能将网站配置为使用ASP.NET4.0,不能打开VS项目
  5. 会议交流 - CCKS2020 | 2020年全国知识图谱与语义计算大会
  6. 滴滴 KDD CUP 2020 赛题详解
  7. python100以内孪生素数_python用递归筛选法求N以内的孪生质数(孪生素数)
  8. java判断输入的格式化_Java的字符串及格式化输入输出
  9. 管理员访客身份登录用户账户,java web/springboot/mybatis实现只能看用户信息但不允许修改
  10. camuda流程引擎如此简单(一)