闭包与装饰器
闭包与装饰器 |
一、闭包
闭包是语法闭包的简称,是引用自由变量的函数。这个被引用的自由变量将与这个函数一同存在,即使已经离开创造它的环境也不例外。所以,闭包是由函数和与其相关的引用环境组合而成的实体。闭包是函数式编程的重要语法结构。Python也支持这一特性。
假设我们通过配置实现打招呼的函数实现闭包:
def GreetingConfig(prefix): def greeting(postfix): print(prefix,postfix) return greeting res = GreetingConfig('Good morning!') res('alex')
在以上程序中,GreetingConfig()函数嵌套greeting()函数,且greeting()函数作为返回值。greeting()函数访问上一级函数的变量prefix。此时,GreetingConfig()函数就是闭包。
闭包的总结:
- 闭包函数必须有嵌套函数;
- 嵌套函数需要引用上一级函数的变量;
- 闭包函数必须返回嵌套函数。
二、装饰器
装饰器本质上就是函数,功能是为其他函数添加附加功能。
通俗的来讲,装饰器就是等于高阶函数+函数嵌套+闭包的综合实现。
装饰器有两大原则:第一,不修改被修饰函数的源代码;第二,不修改被修饰函数的调用方法。
我们通过实例一步步讲解装饰器的功能,首先我们实现一个简单的延时函数,并在程序中调用它,即:
import time
def test(): time.sleep(3) print('test函数运行完毕') test()
执行以上程序的运行结果为:
test函数运行完毕
函数功能非常简单:延时3秒钟,延时后打印,然后在程序中调用该函数。
假设此时想添加一个新功能:计算出test()函数总共的运行时间,为了使源代码保持不变,在这里我们就可以用装饰器来实现,即:
import time
def timmer(func): #func=test def wrapper(): start_time = time.time() func() #就是在运行test() stop_time = time.time() print('运行时间是%s'%(stop_time-start_time)) return wrapper @timmer #相当于test=timmer(test) def test(): time.sleep(3) print('test函数运行完毕') test()
执行以上程序的运行结果为:
test函数运行完毕
运行时间是3.000999927520752
在以上程序中,timmer()函数就是一个装饰器,装饰器完美地实现了它的两大原则,既没有修改源代码,也没用修改调用方法。其中@timmer中的@是Python装饰器的语法糖。@timmer放在test()函数的定义处,相当于执行了语句:test=timmer(test),这就是调用方法为什么没被修改的原因。
1.被装饰的函数带参数
在以上程序中,被装饰的函数test()没有参数,如果有参数,则装饰器该如何构建呢?基于以上程序,修改为如下代码,即:
import time def timmer(func): #func=test def wrapper(*args,**kwargs): start_time = time.time() func(*args,**kwargs) #就是在运行test() stop_time = time.time() print('运行时间是%s'%(stop_time-start_time)) return wrapper @timmer #test=timmer(test) def test(name,age): time.sleep(3) print('test函数运行完毕,名字是%s,年龄是%s'%(name,age)) test('alex',18)
执行以上程序的运行结果为:
test函数运行完毕,名字是alex,年龄是18
运行时间是3.000999927520752
可以看到,当被装饰的函数test()有参数时,可将装饰器的嵌套函数修改为被装饰器函数的形式即可。
若想要使用共同的装饰器来修饰多个不同的函数,则这些不同的函数参数形式不同,装饰器可以通过可变参数(*args,**kwargs)的方式来实现嵌套函数。
2.带参数的装饰器
对于装饰器本身带参数的情况我们应该如何实现,基于前面的例子,为装饰器添加一个bool变量,可以通过变量的真假判断是否调用计时功能,即:
import time def ctime(flag = False): if flag: def timmer(func): #func=test def wrapper(*args,**kwargs): start_time = time.time() func(*args,**kwargs) #就是在运行test() stop_time = time.time() print('运行时间是%s'%(stop_time-start_time)) return wrapper else: def timmer(func): return func return timmer @ctime(flag = False) #test=timmer(test) def test(name,age): time.sleep(3) print('test函数运行完毕,名字是%s,年龄是%s'%(name,age)) test('alex',18) @ctime(flag = True) #test=timmer(test) def test1(name,age): time.sleep(3) print('test1函数运行完毕,名字是%s,年龄是%s'%(name,age)) test1('lisi',20)
执行以上程序的运行结果为:
test函数运行完毕,名字是alex,年龄是18
test1函数运行完毕,名字是lisi,年龄是20
运行时间是3.000999927520752
分析以上程序可以看出,将同一个装饰器ctime用于两个不同的函数:test()和test1()。另外,通过装饰器的参数可以为装饰过程添加判断,@ctime(True)表示进行计时,@ctime(False)不会进行计时,程序运行结果也证明了这一点。
3.装饰器调用顺序
使用多个装饰器修饰同个函数时,装饰器的调用顺序与语法糖@的声明顺序相反。