Python基础:11变量作用域和闭包

一:变量作用域

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

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

 

        在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

 

原文地址:https://www.cnblogs.com/gqtcgq/p/7247204.html