闭包与装饰器

闭包与装饰器

一、闭包

闭包是语法闭包的简称,是引用自由变量的函数。这个被引用的自由变量将与这个函数一同存在,即使已经离开创造它的环境也不例外。所以,闭包是由函数和与其相关的引用环境组合而成的实体。闭包是函数式编程的重要语法结构。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.装饰器调用顺序

使用多个装饰器修饰同个函数时,装饰器的调用顺序与语法糖@的声明顺序相反。

posted @ 2019-07-16 17:06  流浪代码  阅读(289)  评论(0编辑  收藏  举报