1、闭包(closure)
闭包在很多函数式编程语言中都会支持,主要的意义是的内部函数和局部变量可以在全局中进行访问。其主要关注的是变量作用域的问题。Python作为一种面向对象的语言,类的存在完全实现了这种功能,但是它也支持闭包操作,在一些比较简单的操作中类的实现相比闭包来说要复杂很多,同时装饰器也是属于闭包的一种应用。
闭包的定义主要有2种:
从形式来看,如果在一个内部函数里对外部作用域变量(非全局变量)的引用,那么这种行为被称为是闭包(closure)。定义在外部函数内但是在内部函数引用或使用的变量称为自由变量。
def incr(init):
a = init
def inner():
nonlocal a
a += 1
print(a)
return inner
a1 = incr(5)
a2 = incr(5)
a1()
a1()
a1()
a2()
-------------------
6
7
8
6
以上是一个闭包的简单示例,incr函数调用返回内层函数的可执行对象,即a1 = incr(5),返回inner,后面a1()的使用实际上相当于inner()的调用,即调用内层函数对自由变量a的加1计算。 内部函数可以访问外部函数或者全局变量,但是一般情况下不能直接修改,对于全局变量来说需要加global关键字;而对于外部函数变量需要加nonlocal变量。
从实现意义来看,内部函数可以访问外部函数变量的特点很像将外部函数的变量直接“打包”到内部函数中一样,因此:将组成函数的语句以及执行这些语句的环境“打包”在一起时得到的对象称为闭包,它是作为一个整体出现在程序中的,会占用一定的内存空间。以上示例中,在调用函数得到a1/a2后,可以看出是两份独立的结果,并且a1在多次调用时,可以看出闭包像一个整体将自由变量的值保存了下来,这样其实就实现了局部变量可以在全局变量的访问。若声明一个类也可以具有相同的功能。
2、装饰器(decorator)
Python中装饰器是闭包的一个应用,主要用于对某些函数添加某些功能而又不希望破坏原代码,或者给多个函数添加某一个功能,比如记录执行时间,这样写一个装饰器可以用于多个函数而不必逐个修改。
from time import time,sleep
def outer(func):
def inner():
start = time()
func()
print("run time: %d"%(time()-start))
return inner
@outer
def test():
print("test start....")
sleep(5)
print("test end....")
test()
-----------------------------------------
test start....
test end....
run time: 5
函数test需要添加一个统计执行时间的功能,但是又不想在test函数内部去做修改。装饰器提供这样的功能,outer表示一个装饰器,它的参数是一个函数对象,其内层函数inner中调用了传入的参数,实际上本例中test就是一个自由变量。在参数func调用完成后,计算执行时间。而形式上就是在被装饰的函数前面使用@+装饰器,@outer就表示使用outer(test),执行返回inner,而test()就表示对返回的inner进行调用,即inner(),于是实现了函数功能的添加。
python也支持多个装饰器的使用,即在函数上方依次使用@+装饰器,执行时从距离函数最近的装饰器开始依次执行。并且装饰器也支持有参数的函数使用。
from time import time,sleep
def outer1(func):
def inner(*args, **kwargs):
start = time()
func(*args, **kwargs)
print("run time: %d"%(time()-start))
return inner
def outer2(func):
def inner(*args, **kwargs):
func(*args, **kwargs)
print("func args is %s"% (args))
return inner
@outer2
@outer1
def test(time):
print("test start....")
sleep(time)
print("test end....")
test(3)
------------------------------------------
test start....
test end....
run time: 3
func args is 3
以上除添加执行时间(outer1)功能外,还需要将参数打印(outer2)。参数的传入放在内部函数中,即使用inner(*args, **kwargs),因为装饰器返回的是内部的函数,所以参数要加到内部函数参数中。其中*args表示无关键字参数顺序传入、**kwargs表示关键字参数,类似于字典类参数,关于参数的问题暂不讨论。由执行结果可以看出,装饰器依次执行,先执行outer1后执行outer2,相当于outer2(outer1(test))()。