一:变量作用域

变量可以是局部域或者全局域。定义在函数内的变量有局部作用域,在一个模块中最高级别的变量有全局作用域。

全局变量的一个特征是除非被删除掉,否则它们的存活到脚本运行结束,且对于所有的函数,他们的值都是可以被访问的。然而局部变量,仅仅只依赖于定义它们的函数现阶段是否处于活动。

在Python中,变量查找遵循LGB原则,即优先在局部作用域(local scope)中对变量进行查找,失败则在全局作用域(globalscope)中进行查找,最后尝试再内建作用域(build-inscope)内查找,如果还是未找到的话,则抛出NameError异常。所以,局部变量可以“隐藏“或者覆盖一个全局变量。

后来由于闭包和嵌套函数的出现,作用域又增加了外部作用域,这样变量的查找作用域优先级变为:局部、外部、全局和内建。 作用域由def、class、lambda等语句产生,if、try、for等语句并不会产生新的作用域。所以下面的函数:

def foo():
if True:
a = 2
print a

其中的变量a,还是存在于局部作用域,而不是由if语句引入的新的作用域。所以,它对于print语句还是可见的。这与C语言中的情况是不同的。

需要注意下面的情况:

def  scope1_f1():
print global_v
global_v = 'local' if__name__ == "__main__":
global_v = 'global'
scope1_f1()  

运行程序,会得到:UnboundLocalError: localvariable 'global_v' referenced before assignment

这是因为,当在函数内部为一个变量赋值时,该变量就默认为局部变量。所以,上面的scope1_f1函数中,解释器就会认为global_v是一个局部变量,因此赋值引起了异常。下面的情况也类似:

>>>gv = 4
>>>def bar():
... gv += 2...
>>>gv
4 >>>bar()
Traceback(most recent call last):
File "<stdin>", line 1, in<module>
File "<stdin>", line 2, inbar
UnboundLocalError:local variable 'gv' referenced before assignment

在函数bar中,即使换成gv = gv + 2也是一样抛出异常。

局部变量覆盖全局变量的例子:

>>>def foo():
... lv = 2
... gv = 3
... print 'lv is %d, gv is %d'%(lv, gv)
... >>>gv = 4
>>>foo()
lv is2, gv is 3
>>>gv

在函数内部,明确要使用一个已命名的全局变量的时候(避免其被局部变量覆盖),必须使用global 语句。global的语法如下:global var1[, var2[, ... varN]]]

在Python2.1之前的版本中,最多就两个作用域:一个函数的局部作用域和全局作用域。虽然存在多个函数的嵌套,但你不能访问超过两个作用域。

def  foo():
m = 3
def bar():
n = 4
print m + n
print m
bar()

虽然这代码在今天能完美的运行,但在python2.1 之前执行它将会产生错误。

>>>foo()
Traceback(innermost last):

NameError:m 

Python 的lambda 匿名函数遵循和标准函数一样的作用域规则。一个lambda 表达式定义了新的作用域,就像函数定义,所以这个作用域除了局部lambda/函数,对于程序其他部分,该作用域都是不能对进行访问的。

二:闭包

如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。定义在外部函数内的但由内部函数引用或者使用的变量被称为自由变量。闭包在函数式编程中是一个重要的概念。

闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

闭包和函数调用没多少相关,而是关于使用定义在其他作用域的变量。简单的闭包的例子:

def  line_conf():
b = 15
def line(x):
return 2*x+b
return line # return a functionobject my_line= line_conf()
print(my_line(5)) 

line函数中引用了高层级的变量b,但b信息存在于line的定义之外。称b为line的环境变量。事实上,line作为line_conf的返回值时,line中已经包括b的取值(尽管b并不隶属于line)。上面的代码将打印25.

一个函数和它的环境变量合在一起,就构成了一个闭包(closure)。在Python中,所谓的闭包是一个包含有环境变量取值的函数对象。

另一个闭包的例子:

def  counter(start_at=0):
count =[start_at]
def incr():
count[0] += 1
return count[0]
return incr 

创建了一个闭包incr,它现在携带了整个counter()作用域。incr()增加了正在运行的count,然后返回它:

>>>count =counter(5)

>>>print count()
6 >>>print count()
7 >>>count2 =counter(100)
>>>print count2()
101 >>>print count()
8

外部函数退出之后,它的命名空间也就不存在了。但在闭包中,内部函数依然维持了外部函数中自由变量的引用—单元。内部函数不能修改单元对象的值(但是可以引用)。若尝试修改,则解释器会认为它是局部变量。这类似于全局变量和局部变量的关系。如果在函数内部修改全局变量,必须加上global声明,但是对于自由变量,尚没有类似的机制。所以,只能使用列表。(python3中引入了关键字:nonlocal)

所以,在上面的例子中,使用了列表,而不是直接使用该变量本身。如果改为:

def  counter2(start_at=0):
count = start_at
def incr():
count += 1
return count
return incr 

则在调用

count= counter(5)
count()

会引发异常,这与上一节中修改全局变量时的异常本质一样:

count += 1

UnboundLocalError: local variable 'count'referenced before assignment

可以通过函数的func_closure属性来追踪自由变量:

output = '<int %r id=%#0x val=%d>'

w = x= y = z = 1

def  f1():
x = y = z = 2 def f2():
y = z = 3 def f3():
z = 4
print output % ('w', id(w), w)
print output % ('x', id(x), x)
print output % ('y', id(y), y)
print output % ('z', id(z), z) clo = f3.func_closure
if clo:
print "f3 closurevars:", [str(c) for c in clo]
else:
print "no f3 closurevars"
f3() clo = f2.func_closure
if clo:
print "f2 closurevars:", [str(c) for c in clo]
else:
print "no f2 closurevars"
f2() clo = f1.func_closure
if clo:
print "f1 closure vars:", [str(c)for c in clo]
else:
print "no f1 closure vars" f1() 

运行结果如下:

no f1 closure vars

f2  closure vars: ['<cell at 0x0164BD10: int object at 0x0157ACC4>']

f3  closure vars: ['<cell at 0x0164BD10: int object at 0x0157ACC4>', '<cell at 0x0164BDB0: int object at0x0157ACB8>']

<int'w' id=0x157acd0 val=1>
<int'x' id=0x157acc4 val=2>
<int'y' id=0x157acb8 val=3>
<int'z' id=0x157acac val=4>

f1创建局部变量x,y 和z。它的内部函数f2创建了局部变量y和z。f2的内部函数f3创建了局部变量z。

f3中访问了w,x,y,z。其中w为全局变量,z为局部变量,所以,x和y为f3访问的自由变量,记录在f3.func_closure中。

f2虽然没有直接访问任何变量,但因为其内部函数f3访问了w, x, y, z。所以,也认为f2访问了这些变量。其中,w为全局变量,y、z为局部变量。所以,只有x为f2访问的自由变量,记录在f2.func_closure中。

参考:

https://docs.python.org/2/faq/programming.html?highlight=unboundlocalerror#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value

http://www.cnblogs.com/vamei/archive/2012/12/15/2772451.html

Python基础:11变量作用域和闭包的更多相关文章

  1. 十一. Python基础(11)—补充: 作用域 & 装饰器

    十一. Python基础(11)-补充: 作用域 & 装饰器 1 ● Python的作用域补遗 在C/C++等语言中, if语句等控制结构(control structure)会产生新的作用域 ...

  2. Python入门笔记(22):Python函数(5):变量作用域与闭包

    一.全局变量与局部变量 一个模块中,最高级别的变量有全局作用域. 全局变量一个特征就是:除非被删除,否则他们存活到脚本运行结束,且对于所有的函数都可访问. 当搜索一个标识符(也称变量.名字等),Pyt ...

  3. Python学习(21):Python函数(5):变量作用域与闭包

    转自 http://www.cnblogs.com/BeginMan/p/3179040.html 一.全局变量与局部变量 一个模块中,最高级别的变量有全局作用域. 全局变量一个特征就是:除非被删除, ...

  4. Python基础之变量作用域

    一.分类: 二.变量名的查找规则: 三.局部变量: 四.全局变量: 五.global语句: 六.nonlocal语句: 七.基础代码: # 全局变量:当前.py文件内部都可访问 g01 = 100 d ...

  5. python 变量作用域、闭包

    先看一个问题: 下面代码输出的结果是0,换句话说,这个fucn2虽然已经用global声明了variable1,但还是没有改变变量的值 def func1(): variable1=0 def fun ...

  6. 九. Python基础(9)--命名空间, 作用域

    九. Python基础(9)--命名空间, 作用域 1 ● !a 与 not a 注意, C/C++可以用if !a表示if a == 0, 但是Python中只能用if not a来表示同样的意义. ...

  7. python 函数及变量作用域及装饰器decorator @详解

    一.函数及变量的作用   在python程序中,函数都会创建一个新的作用域,又称为命名空间,当函数遇到变量时,Python就会到该函数的命名空间来寻找变量,因为Python一切都是对象,而在命名空间中 ...

  8. 『Python基础-11』集合 (set)

    # 『Python基础-11』集合 (set) 目录: 集合的基本知识 集合的创建 访问集合里的值 向集合set增加元素 移除集合中的元素 集合set的运算 1. 集合的基本知识 集合(set)是一个 ...

  9. Python 名称空间与作用域、闭包与装饰器

    Python 的名称 Python 的名称(Name)是对象的一个标识(Identifier).我们知道,在 Python 里面一切皆对象,名称就是用来引用对象的.说得有点玄乎,我们以例子说明. 例如 ...

随机推荐

  1. Django项目:CRM(客户关系管理系统)--26--18PerfectCRM实现King_admin搜索关键字

    search_fields = ('name','qq',) 登陆密码设置参考 http://www.cnblogs.com/ujq3/p/8553784.html search_fields = ( ...

  2. 直接在安装了redis的Linux机器上操作redis数据存储类型--String类型

    一.概述: 字符串类型是Redis中最为基础的数据存储类型,它在Redis中是二进制安全的,这便意味着该类型可以接受任何格式的数据,如JPEG图像数据或Json对象描述信息等.在Redis中字符串类型 ...

  3. jQ-点击查看更多

    <style type="text/css"> .hi { width: 200px; height: 18vw; background-color: pink; fo ...

  4. proteus 8.8 直装版提示Symbol $MKRORIGIN used but not found in libraries 安装后没有库

    用管理员运行程序,然后再通过菜单打开仿真文件是没问题. 解决方法:通常的安装目录是C:\Program Files (x86)\Labcenter Electronics\Proteus 8 Prof ...

  5. git中由readme.md文件引发的问题

    在GitHub上建立一个仓库并且添加了readme.txt文件. 无论是push前先将远程仓库pull到本地仓库,还是强制push都会弹出这个问题. Github 禁用了TLS v1.0 and v1 ...

  6. HDU5583 Kingdom of Black and White

    Kingdom of Black and White Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Ja ...

  7. Codeforces 851D Arpa and a list of numbers

    D. Arpa and a list of numbers time limit per test 2 seconds memory limit per test 256 megabytes inpu ...

  8. NOIP模拟 6.26

    T1 子矩阵 题目描述 小A有一个N×M的矩阵,矩阵中1~N*M这(N*M)个整数均出现过一次.现在小A在这个矩阵内选择一个子矩阵,其权值等于这个子矩阵中的所有数的最小值.小A想知道,如果他选择的子矩 ...

  9. Leetcode622.Design Circular Queue设计循环队列

    设计你的循环队列实现. 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环.它也被称为"环形缓冲器". 循环队列的一个好处是 ...

  10. 初次接触python的re模块

    刷CF的时候,看到一个简单的题目,可以用来练练正则表达式 于是乎找到了re.sub的用法,说明如下 re.sub: (pattern, repl, string, count=0, │       f ...