函数的进阶——装饰器

老样子,先来三个问题:

1. 什么是装饰器

2. 为什么要有装饰器

3. 怎么用装饰器

一、什么是装饰器

装饰器从字面意思理解来说就是具有装饰功能的器具。在python中,装饰器就是用来给被装饰对象(函数)增加功能(装饰)的工具。

装饰器本身可以是任意可调用的对象——函数

被装饰对象也可以是任意可调用的对象——函数

装饰器:写一个函数给另一个函数增加新功能

二、为什么要有装饰器

当我们写的程序正式进入运营阶段,我们需要遵守开放封闭原则。

开放封闭原则:程序上线以后就应该对修改封闭,对扩展开放

           修改封闭:不能修改功能性函数的源代码

                              不能修改功能性函数的调用方式

           扩展开放:可以为原有的功能添加新功能

这两个原则看上去挺矛盾的,其实仔细想一想是可以想通的。我们的程序上线以后,有客户在正常使用,当我们更改了函数源代码,可能会导致更改不小心出错,使程序崩溃。调用方式更改显得麻烦,要更改每次调用函数的地方,也容易出错。而我们的程序在刚出生时并不是十全十美的,我们必须要为其添加一些功能。

这个时候装饰器就派上了用场。

三、怎么用装饰器

我们一步步的将装饰器讲解清楚。首先,我们定义一个函数

import time, random

def msg():
    print('I have something to tell you, please wait a moment.')
    time.sleep(random.randint(1,5))
    print('I like you.')

msg()
# 该函数会随机休眠1-5秒钟。

现在,我们想要给该函数增加一个计时的功能,看看函数具体运行了多久。

import time, random

def msg():
    print('I have something to tell you, please wait a moment.')
    time.sleep(random.randint(1,5))
    print('I like you.')

start_time = time.time()
msg()
stop_time = time.time()
print('Run time is %s' % (stop_time - start_time))

这样子我们就实现了更能了,但是这样很麻烦,并且如果要重复使用计时功能,就要写重复代码,所以我们想到了将计时功能封装成一个函数

def wrapper():
    start=time.time()
    msg() 
    stop=time.time()
    print('Run time is %s' % (stop-start))

wrapper()    

这样又出现了新问题:我们更改了函数的调用方式,并且我们将wrapper函数写死了,只能对内部调用的msg函数进行计时,不符合我们想要的结果。这时,就需要我们将前面的知识点全部串起来:函数对象,函数嵌套,闭包函数,名称空间与作用域。

def timmer(func):    # 这里运用的就是昨天学的闭包函数知识点
    def wrapper():     # 定义wrapper函数
        start=time.time()
        func() 
        stop=time.time()
        print('Run time is %s' % (stop-start))
    return wrapper    # 将wrapper函数的内存地址作为返回值返回。

a = timmer(msg)      # 这里的a指向wrapper函数的内存地址
a()        # a()就可以触发执行wrapper函数的代码块

我们将msg函数的内存地址作为参数传入timmer函数,这样我们就可以在wrapper函数中访问到msg这个参数。(闭包函数作用域知识点)。然后我们将wrapper函数的内存地址作为返回值返回,这样我们执行timmer函数就可以得到一个函数的内存地址。

def timmer(func):    # 这里运用的就是昨天学的闭包函数知识点
    def wrapper():     # 定义wrapper函数
        start=time.time()
        func() 
        stop=time.time()
        print('Run time is %s' % (stop-start))
    return wrapper    # 将wrapper函数的内存地址作为返回值返回。

msg = timmer(msg)      # 作为参数的msg是我们定义的msg函数的内存地址
                       # 作为变量名的msg是指向wrapper函数的内存地址
msg()        # 此时我们这样写看似在调用msg函数,但实际上是调用了wrapper函数
            # 这样子我们就实现了偷梁换柱,在外观上没有更改msg函数的调用方式。

看上面一段代码,我们把上面说的两个问题全部解决了。外观上没有更改msg的调用方式,并且实现了装饰器的多用性。可以将任意的函数名传入,实现给任意函数增加计时的功能。

可以看出,当传入的函数没有参数,没有返回值时,我们写的装饰器还是很完美的。但是当传入的函数有参数或者有返回值时,就先得略有不足。我们接下来再来修改一下:

def timmer(func):
    def wrapper(*args, **kwargs):
        strat_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        return res
    return wrapper

我们对wrapper函数写了两个可变长参数,这两个参数可以接收一切传进来的实参并原封不动的将其传递给func函数。当我们传入的函数需要参数时,*args与**kwargs可以全部接收,不需要参数时,可以不传入参数,完美实现了传入函数的参数需求。然后我们在wrapper函数内将func的运行结果赋值给res,再将res当做返回值返回,想一下就可以明白,wrapper函数的返回值和func函数的返回值是相同的。

这样,一个八分完美的装饰器就写好了。为什么说是八分完美呢,因为还有一个知识点咱们还没有提到。

装饰器也是可以有参数的。

在说含参装饰器之前,我们先来说一下装饰器的使用方法:语法糖

定义好装饰器以后,我们只需要在被装饰函数的正上方来一个@+装饰器名:

import time, random

def timmer(func):
    def wrapper(*args, **kwargs):
        strat_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        return res
    return wrapper

@timmer
def msg():
    print('I have something to tell you, please wait a moment.')
    time.sleep(random.randint(1,5))
    print('I like you.')

msg()

@+装饰器名字就是装饰器的正确用法:@timmer会帮我们实现:正下方的函数名作为参数传入装饰器。再将函数名作为变量名去接收装饰器的返回值。

接下来我们说一下含参装饰器:

想一下,我们在什么情况下会用到给装饰器传入参数。在我们的装饰器内部需要使用一个参数的时候才会传入一个参数。而我们看装饰器的代码:

def timmer(func):
    def wrapper(*args, **kwargs):
        strat_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        return res
    return wrapper

可以看到,不管是wrapper还是timmer,都无法再传入参数,他们两个的参数已经固定死了,再传入其他参数的话就会出错。所以我们想到了用闭包函数给装饰器传参数。

说白了含参装饰器就是一个三层的闭包函数

def timmer_2(x):
    def timmer(func):
        def wrapper(*args, **kwargs):
            strat_time = time.time()
            print(x)
            res = func(*args, **kwargs)
            stop_time = time.time()
            return res
        return wrapper
    return timmer

通过外包一层timmer_2,我们将变量x作为参数传进了装饰器内部。不管内部需要什么参数,我们都可以通过最外层将参数传进去。这样就实现了装饰器对参数的需求。

我们就学会了一个十全十美的含参装饰器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Noah Ren

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值