概述
python的终极使用是装饰器,但在使用装饰器前需要掌握闭包,但是掌握闭包之前需要掌握嵌套函数,下面由浅入深来说下他们的关系。
嵌套函数
很多高级语言都支持函数的嵌套的定义(划重点:是定义,不是调用)。嵌套定义指在函数内部定义另一个函数, 该嵌套函数的作用域只能在父函数范围内。
def printMsg():
print("hello world")
def printMsg2("goog night xm"):
print("")
printer()
上述即为简单的嵌套函数,对于嵌套函数,它可以访问到外层作用域中的变量(专业术语叫:非局部变量 nonlocal,也称之为自由变量。)。示例如下:
def printMsg():
msg = "hello xm"
def printer():
print(msg)
printer()
闭包
简单来说就是 你调用了一个函数A ,然后函数A给你返回了函数B给你。这个返回的函数B就叫做闭包(嵌套函数在父函数内部就直接将得到结果。而闭包是返回子函数)。你在调用函数A的时候传递的参数就是自由变量。
def func(name):
def inner_func(age):
print 'name:', name, 'age:', age
return inner_func
bb = func('the5fire')
bb(26)
# >>> name: the5fire age: 26
这里面调用func的时候就产生了一个闭包——inner_func,并且该闭包持有自由变量——name,因此这也意味着,当函数func的生命周期结束之后,name这个变量依然存在,因为它被闭包引用了,所以不会被回收。
另外再说一点,闭包并不是Python中特有的概念,所有把函数做为一等公民的语言均有闭包的概念。不过像Java这样以class为一等公民的语言中也可以使用闭包,只是它得用类或接口来实现。
- nonlocal 语句
在 python 的函数内,可以直接引用外部变量,但不能改写外部变量,因此如果在闭包中直接改写父函数的变量,就会发生错误:
def cnt(param):
dount = 0
def couter():
count += 1
print("I'm", param, "No.", str(count))
return counter
dobi = cnt("dobi")
dobi()
dobi()
dobi()
#UnboundLocalError:local variable 'count' referenced before assignment
在 python 2 中可以在函数内使用 global 语句,但全局变量在任何语言中都不被提倡,因为它很难控制,python 3 中引入了 nonlocal 语句解决了这个问题:
def cnt(param):
dount = 0
def couter():
nonlocal count
count += 1
print("I'm", param, "No.", str(count))
return counter
dobi = cnt("dobi")
dobi()
dobi()
dobi()
#I'm, dobi No.1
#I'm, dobi No.2
#I'm, dobi No.3
Nonlocal 与 global 的区别在于 nonlocal 语句会去搜寻本地变量与全局变量之间的变量,其会优先寻找层级关系与闭包作用域最近的外部变量。
装饰器
装饰器就是一种闭包的应用(对原有功能升级,而省略了闭包的的调用过程),只不过其传递的是函数:
def makebold(fn):
def wrapped():
return "<b>" + fn() + "</b>"
return wrapped
def makeitalic(fn):
def wrapped():
return "<i>" + fn() + "</i>"
return wrapped
@makebold # 等同于 hello = makebold(makeitalic(hello))
@makeitalic #等同于 hello = makeitalic(hello)
def hello():
return "hello xm"
print(hello())
# 上述代码执行结果: <b><i>hello xm</i></b>
@makeitalic 装饰器将函数 hello 传递给函数 makeitalic,函数 makeitalic 执行完毕后返回被包装后的 hello 函数,而这个过程其实就是通过闭包实现的。@makebold 也是如此,只不过其传递的是 @makeitalic 装饰过的 hello 函数,因此最后的执行结果 在 外层,这个功能如果不用装饰器,其实就是显式的使用闭包:
def makebold(fn):
def wrapped():
return "<b>" + fn() + "</b>"
return wrapped
def makeitalic(fn):
def wrapped():
return "<i>" + fn() + "</i>"
return wrapped
@makebold # 等同于 hello = makebold(makeitalic(hello))
@makeitalic #等同于 hello = makeitalic(hello)
def hello():
return "hello xm"
hello = makeitalic(hello)
hello = makebold(hello)
print(hello())
#<b><i>hello xm</i></b>
再说闭包
闭包的最大特点是可以将父函数的变量与内部函数绑定,并返回绑定变量后的函数(也即闭包),此时即便生成闭包的环境(父函数)已经释放,闭包仍然存在,这个过程很像类(父函数)生成实例(闭包),不同的是父函数只在调用时执行,执行完毕后其环境就会释放,而类则在文件执行时创建,一般程序执行完毕后作用域才释放,因此对一些需要重用的功能且不足以定义为类的行为,使用闭包会比使用类占用更少的资源,且更轻巧灵活,现举一例:假设我们仅仅想打印出各类动物的叫声,分别以类和闭包来实现:
class Animal(object):
def __init__(self, animal):
self.animal = animal
def sound(self, voice):
print(self.animal, ":", voice, "...")
dog = Animal("dog")
dog.sound("wangwang")
dog.sound("wowo")
# dog: wangwang ...
# dog: wowo ...
def voice(animal):
def sound(voc):
print(animal, ":", voc, "...")
return sound
dog = voice("dog")
dog.sound("wangwang")
dog.sound("wowo")
# dog: wangwang ...
# dog: wowo ...
可以看到输出结果是完全一样的,但显然类的实现相对繁琐,且这里只是想输出一下动物的叫声,定义一个 Animal 类未免小题大做,而且 voice 函数在执行完毕后,其作用域就已经释放,但 Animal 类及其实例 dog 的相应属性却一直贮存在内存中:
而这种占用对于实现该功能后,则是没有必要的。
除此之外,闭包还有很多其他功能,比如用于封装等,另外,闭包有效的减少了函数参数的数目,这对并行计算非常有价值,比如可以让每台电脑负责一个函数,然后串起来,实现流水化的作业等。