【python闭包详解】Python中的闭包介绍及实例

更新时间:2019-12-02    来源:python    手机版     字体:

【www.bbyears.com--python】

Python中的闭包介绍

闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,闭包是由函数和与其相关的引用环境组合而成的实体。

在开始介绍闭包之前先看看Python中的namespace。


Python中的namespace

Python中通过提供 namespace 来实现重名函数/方法、变量等信息的识别,其一共有三种 namespace,分别为:

    local namespace: 作用范围为当前函数或者类方法
    global namespace: 作用范围为当前模块
    build-in namespace: 作用范围为所有模块

当函数/方法、变量等信息发生重名时,Python会按照 "local namespace -> global namespace -> build-in namespace"的顺序搜索用户所需元素,并且以第一个找到此元素的 namespace 为准。

同时,Python中的内建函数locals()和globals()可以用来查看不同namespace中定义的元素。

看一个例子:

python;toolbar:false\">s = "string in global"
num = 99
def numFunc(a, b):
    num = 100
    print "print s in numFunc: ", s
    
    def addFunc(a, b):
        s = "string in addFunc"
        print "print s in addFunc: ", s
        print "print num in addFunc: ", num
        print "locals of addFunc: ", locals()
        print 
        return "%d + %d = %d" %(a, b, a + b)
        
    print "locals of numFunc: ", locals()
    print 
    return addFunc(a, b)
    
    
numFunc(3, 6)    
print "globals: ", globals()



上面代码的结果为:



代码中通过locals()和globals()获取了不同namespace中定义的元素,当在"numFunc"函数中访问s变量的时候,由于local namespace中没有定义s,所以会找到global中的s;但是,由于"addFunc"的local namespace中有s,所以可以直接使用。


Python创建闭包

下面通过一个例子一步一步分析一下Python如何实现闭包。


一个闭包的例子

下面看一个闭包的简单的例子,例子中定义了一个可以配置打招呼的函数"greeting_conf",在这个函数中,内嵌了一个"greeting"的内嵌函数:


python;toolbar:false\">def greeting_conf(prefix):
    def greeting(name):
        print prefix, name
    return greeting
    
mGreeting = greeting_conf("Good Morning")
mGreeting("Wilber")
mGreeting("Will")
print
aGreeting = greeting_conf("Good Afternoon")    
aGreeting("Wilber")
aGreeting("Will")


代码的结果为:



上面的代码有下面几个注意点:

    "greeting"函数访问了non-local的变量"prefix",根据前面namespace的介绍,这点是完全合法的
    变量"prefix"并没有随着函数的退出而销毁,反而是生命周期得到了延长

下面看看为什么变量"prefix"生命周期得到了延长?


__closure__属性

在Python中,函数对象有一个__closure__属性,我们可以通过这个属性看看闭包的一些细节。


def greeting_conf(prefix):
    def greeting(name):
        print prefix, name
    return greeting
    
mGreeting = greeting_conf("Good Morning")      
print dir(mGreeting)
print mGreeting.__closure__
print type(mGreeting.__closure__[0])
print mGreeting.__closure__[0].cell_contents


通过__closure__属性看到,它对应了一个tuple,tuple的内部包含了cell类型的对象。

对于这个例子,可以得到cell的值(内容)为"Good Morning",也就是变量"prefix"的值。



从这里可以看到闭包的原理,当内嵌函数引用了包含它的函数(enclosing function)中的变量后,这些变量会被保存在enclosing function的__closure__属性中,成为enclosing function本身的一部分;也就是说,这些变量的生命周期会和enclosing function一样。

闭包和函数

闭包只是在表现形式上跟函数类似,但实际上不是函数。

def greeting_conf(prefix):
    def greeting(name):
        print prefix, name
    return greeting
    
mGreeting = greeting_conf("Good Morning")      
print "function name is:", mGreeting.__name__
print "id of mGreeting is:", id(mGreeting)
print
aGreeting = greeting_conf("Good Afternoon")  
print "function name is:", aGreeting.__name__
print "id of aGreeting is:", id(aGreeting)



从代码的结果中可以看到,闭包在运行时可以有多个实例,不同的引用环境(这里就是prefix变量)和相同的函数(这里就是greeting)组合可以产生不同的实例。

Python中怎么创建闭包

在Python中创建一个闭包可以归结为以下三点:

    闭包函数必须有内嵌函数
    内嵌函数需要引用该嵌套函数上一级namespace中的变量
    闭包函数必须返回内嵌函数

通过这三点,就可以创建一个闭包,是不是想到了上一篇中介绍的Python装饰器。没错,Python装饰器就是使用了闭包。



python实例

看概念总是让人摸不着头脑,看几个python小例子就会了

例1

def make_adder(addend):
    def adder(augend):
        return augend + addend
    return adder
p = make_adder(23)
q = make_adder(44)
print p(100)
print q(100)


运行结果:

123
144

分析一下:

我们发现,make_adder是一个函数,包括一个参数addend,比较特殊的地方是这个函数里面又定义了一个新函数,这个新函数里面的一个变量正好是外部make_adder的参数.也就是说,外部传递过来的addend参数已经和adder函数绑定到一起了,形成了一个新函数,我们可以把addend看做新函数的一个配置信息,配置信息不同,函数的功能就不一样了,也就是能得到定制之后的函数.

再看看运行结果,我们发现,虽然p和q都是make_adder生成的,但是因为配置参数不同,后面再执行相同参数的函数后得到了不同的结果.这就是闭包.

例2

def hellocounter (name):
    count=[0] 
    def counter():
        count[0]+=1
        print 'Hello,',name,',',str(count[0])+' access!'
    return counter
hello = hellocounter('ma6174')
hello()
hello()
hello()


执行结果

Hello, ysisl , 1 access!
Hello, ysisl , 2 access!
Hello, ysisl , 3 access!

分析一下

这个程序比较有趣,我们可以把这个程序看做统计一个函数调用次数的函数.count[0]可以看做一个计数器,没执行一次hello函数,count[0]的值就加1。也许你会有疑问:为什么不直接写count而用一个列表?这是python2的一个bug,如果不用列表的话,会报这样一个错误:

UnboundLocalError: local variable 'count' referenced before assignment.

什么意思?就是说conut这个变量你没有定义就直接引用了,我不知道这是个什么东西,程序就崩溃了.于是,再python3里面,引入了一个关键字:nonlocal,这个关键字是干什么的?就是告诉python程序,我的这个count变量是再外部定义的,你去外面找吧.然后python就去外层函数找,然后就找到了count=0这个定义和赋值,程序就能正常执行了.

python3 代码

def hellocounter (name):
    count=0 
    def counter():
        nonlocal count
        count+=1
        print 'Hello,',name,',',str(count[0])+' access!'
    return counter
hello = hellocounter('ma6174')
hello()
hello()
hello()


关于这个问题的研究您可以参考http://linluxiang.iteye.com/blog/789946

例3

def makebold(fn):
    def wrapped():
        return "" + fn() + ""
    return wrapped
def makeitalic(fn):
    def wrapped():
        return "" + fn() + ""
    return wrapped
@makebold
@makeitalic
def hello():
    return "hello world"
print hello()


执行结果

hello world

简单分析

怎么样?这个程序熟悉吗?这不是传说的的装饰器吗?对,这就是装饰器,其实,装饰器就是一种闭包,我们再回想一下装饰器的概念:对函数(参数,返回值等)进行加工处理,生成一个功能增强版的一个函数。再看看闭包的概念,这个增强版的函数不就是我们配置之后的函数吗?区别在于,装饰器的参数是一个函数或类,专门对类或函数进行加工处理。

python里面的好多高级功能,比如装饰器,生成器,列表推到,闭包,匿名函数等,开发中用一下,可能会达到事半功倍的效果!

本文来源:http://www.bbyears.com/jiaocheng/81858.html

热门标签

更多>>

本类排行