python中的函数本身就是对象,所以可以作为参数拿来传递。同时其允许函数的层级嵌套定义,使得灵活性大大增加。
闭包
闭包的定义:将函数的语句块与其运行所需要的环境打包到一起,得到的就是闭包对象。比如这样,在outer中写下一些变量,作为inner的参数,inner本身就像是个类方法然后在outer中定义的变量像成员变量。其实闭包就是为面向过程的编程语言提供了一种面向对象编程的思想方法。
以下是一些闭包中的问题&从语言机理出发的解释,顺便学习下python的机理。(本来打算和笔记本上一样按点整理,但是发现之前对这块知识的理解太青涩,矛盾百出。。一下子找不到头绪,决定可能比较凌乱地罗列一下几个点。)
首先来看一个例子:
def outer():
para = [1]
def inner():
para.append(2)
print id(para),para
print id(para),para
return inner
if __name__ == '__main__':
inner = outer()
inner()
outer()
#输出
49352136 [1]
49352136 [1, 2]
49495880 [1]
首先,前两个输出的id一样,说明outer中的para确实原封不动地拿去给inner用了(至于什么叫原封不动,下文中有解释),且inner对其做了append操作之后para的值确实改变了。但是第三次和前两次的id又不同,这说明每调用一次outer,python就会重新初始化一个para,此时的para已经不是之前的那个para,id都不一样值自然也就回到了最开始的[1]了。
接下来如果把para.append(2)给换成para+=[2]的话,也就是:
def outer():
para = [1]
def inner():
para += [2]
print id(para),para
print id(para),para
return inner
if __name__ == '__main__':
inner = outer()
inner()
outer()
#输出
47189448 [1]
(报错UnboundLocalError: local variable 'para' referenced before assignment)
首先第一条输出还是能出现,说明了一个问题,python一直到在调用函数的时候才会开始解释函数中的语句。对于这个示例来说,第一次执行outer的时候,解释器碰到了def inner时并没有进入inner去解释,所以即使在inner有错误python还是会输出第一行的。*(当然SyntaxError是不行的,解释器开始解释时会无关具体语法地检查一遍syntax,通过检查才开始执行的)
其次,为什么+=语句会报错?这和python中的赋值语句的机理有关系。赋值语句的左边,如果直接是个变量名,那么python会默认它是当前最小的命名空间里的一个变量,然后进行一些操作。这个过程中,实际上python自动为你的变量做了声明了。换句话,python中的赋值语句相当于其他语言中先声明再赋值。而这个示例中的para+=[2]相当于para = para+[2] //(啊啊啊啊woc,这个问题我解释不了了orz)
那么如果在para+=[2]之前加上一句para=[3]会怎么样?这时你会发现,不仅para+=[2]能顺利完成,而且三次打印出来的para的id是一样的!这说明了当inner在给一个和外层函数变量同名的变量赋值时,默认这个变量就是外层函数的那个变量!这似乎就解释上文中的那个原封不动的意思?//至于为什么inner里面加一句para的赋值语句之后会连第三次打印出来的id都和前两次一样这点存疑
另外,还想补充一下关于内层函数用到的一些外层函数的变量赋值时机的问题。正如前面所说,只有当内层函数被调用的时候才python会去解析内层函数的语句。而所谓的解析语句的一个重要步骤就是去给被调用的外层函数的变量赋值。所以在下面这样一个示例中会出现如下情况:
def main():
flist = []
for i in range(3):
def foo(x):
print x+i
flist.append(foo)
for func in flist:
func(2)
#输出是
4
4
4
而不是
2
3
4
很显然,我原本的意图可能是让flist中存的3个foo函数对象有不同的功能,即在定义时3个foo函数的i是不同的。但是其实flist.append的时候,其append的只是一个函数对象(或者说只有函数语句的逻辑),并没有具体的指明其调用的外层函数变量i的具体值。因为每个foo的函数语句是相同的,所以在flist中的3个foo对象也是毫无差别。而在调用的时候,python才会给i去赋值,而i的值就是range(3)中最后一个赋给i的值2.所以输出的三个都是4而不是2,3,4。要想解决这个问题,让函数在被定义的时候就记住这个i的值,也很简单,就是用参数的默认值。这点在python零碎III中提到过,即函数参数的默认值在定义时就会被函数保存下来的。比如把foo函数改造成def (x,y=i):print x+y即可
闭包的应用场景主要有下(其实python既然支持面向对象编程,肯定用面向对象的思想来写更加简洁易懂。不一定用,就当是长个知识吧。。):
1. 当闭包内层函数执行完之后要更新环境再做进一步处理的情况:
def create(pos = [0]):
def move(step):
pos[0]+=step
return pos
return move
>>>p = create() #创造一个在起点的对象
>>>print p(1) #向前走一步
[1] #返回当前位置
>>>print p(2) #再向前走两步
[3] #返回当前位置
2. 根据不同外层函数变量得到不同的闭包结果等等
哎。。原本以为已经差不多弄懂这个闭包了,可是今天再看看发现还是有很多不自洽的地方。不过好在python可以用class!闭包。。算了,继续往下吧,装饰器肯定比闭包用的更多
装饰器
来看这样一个闭包结构:
def wrapper(func):
def powerup():
print "Something before"
res = func()
print "Something after"
return res
return powerup
对于传入wrapper的任何一个函数对象,它都得到了powerup函数对它的加强(在运行它之前会先进行预处理,运行之后会进行后处理)。如果让我们需要加强的一个函数function = wrapper(function)的话,function就“进化”成一个加强版的函数了。
在这个示例中,wrapper就是一个装饰器,在python2.4之后,提供了@wrapper的方法来表示一种装饰器。而在@wrapper下面def的函数就是需要加强的函数了。
如果一个装饰器带参数,那就是说明wrapper这个函数除了func这个参数本身,还带有一些其他参数,比如:
@eventhandler("BUTTON")
def handle_button(msg):
#一些处理
#相当于:
def handle_button(msg):
#一些处理
temp = eventhandler("BUTTON")
handle_button = temp(handle_button)
#简单地说,这个参数的意义就在于根据不同的参数,装饰器函数会给出不同地加强器(if you will)来加强需要加强的函数,自然也就可以得到不同版本的加强函数了。
所以在自定义带参数的装饰器函数的时候,要注意得包三层。第一层是@后面写的那个函数,其接受参数之后返回一个装饰器函数,这个装饰器函数才是之前所谓不带参数的装饰器中的那个装饰器函数。
针对多个装饰器共同装饰的情况:
@a
@b
@c
def func():
...
#相当于:
func = a(b(c(func)))
*装饰器也可以用于修饰类,但是在修饰类时就要保证装饰器函数返回的是一个类的抽象对象了(正所谓python之中万物皆对象,类本身也是个对象哦)
在fuctools内建模块中含有wraps这个函数。一般而言在自定义装饰器的时候可以在内层函数开始之前写一个@wraps(func),这样可以保证在使用装饰器时被装饰函数的__name__,__doc__等属性得以保留,看下面的例子中加入和注释@wraps的区别: