Python装饰器

先说概念,Python装饰器 (decorator) 本事上是对函数闭包 (fuction closure) ** 的语法糖(Syntacic suger)** 。 没错,一句话里出现了三个我从来没有见过的高大上的名词。 所以说要学明白什么是装饰器,就要搞清楚这三个概念。

函数闭包

什么是函数闭包:一个函数,其参数和返回值都是函数(众所周知Python的函数可以当指针变量来使用)eg.

# 我想要打印一句话并统计打印这句话花费的时间
# 第一种, 打印与计算时间耦合
def printsome():
    a = time.time()
    print('This is a print')
   	return time.time() - a
if __name__ == '__main__':
    printsome()
    
# 第二种解耦但是使用主函数可读性模糊
def printsome():
	print('This is a print')
def cal_time(some_fuc):
    a = time.time()
    some_fuc()
    return time.time() - a
if __name__ == '__main__':
    cal_time(printsome)
    
# 第三种,闭包,逻辑上符合了,但是每次调用之前需要主要逻辑函数的赋值,真的必死强迫症
import time
def printsome():
    print('This is a print')
def print_and_count(some_fuc):
    def cal_time():
        a = time.time()
        some_fuc()
        return time.time() - a
    return cal_time
if __name__ == '__main__':
    printsome = print_and_count(printsome)
    printsome()

所以说我们来充重看一眼闭包函数, 就可以总结出这样的特点:1. 闭包函数本质上是一个函数, 2. 闭包函数的传入和返回都是函数的指针 3. 闭包函数的返回值是对传入函数进行增强后的结果。关于缺点我上面的注释也已经标出了,那就是每次使用前的赋值对强迫症及其不友好。所以说为了改掉这个每次赋值的臭毛病,让代码变得美观,就出现了我们的装饰器。

装饰器与语法糖

关于装饰器:前面的代码没什么区别,但是在最后调用的时候不需要我们的赋值,而是变成下面这种格式

# 和上面函数是一样的只不过换了个名字
def countime(some_fuc):
    def cal_time():
        a = time.time()
        some_fuc()
        print('一共用时' + str(time.time() - a))
    return cal_time
@countime
def printsome():
    print('hello')
if __name__ == "__main__":
    printsome()

这样就成了我们的装饰器。装饰器对我们的函数进行了增强。二者正体现了Python中语法糖的特性。语法糖呢,是指计算机语言中添加的某种语法,这种语法对语言的功能没有影响, 但是更让方便我们的使用。换句话说:语法糖没有添加新功能,只是一种更方便的写法,且语法糖可以完全等价地转换为非语法糖的代码

但是,装饰器只能在第一次调用被装饰函数时进行增强。什么意思呢,难道是我第二次调用函数就没有语法糖的增强BUFF加持了么?还是实践出真章:

if __name__ == "__main__":
    printsome()
    printsome()
# output
hello
一共用时0.0
hello
一共用时0.0

不对啊,我调用了两次被语法糖修饰的函数,但是两次都打印了语法糖的,那很明显,我们理解的第一次不是指这个第一次,用我自己的话来说,就是一个函数写了一个语法糖过后,他就只会停留在增强一次的这个状态,多次调用也是之增强一次的状态,所以第二次调用不会输出两个一共用时。当然,如果你嵌套多个语法糖的时候,有几个语法糖嵌套就会增强几次:

@countime
@countime
@countime
def printsome():
    print('hello')
if __name__ == "__main__":
    printsome()
# output
hello
一共用时0.0
一共用时0.0
一共用时0.0

函数的传参

上面的例子都是不需要传参的函数,一段我们需要传参,就会发现:

def countime(some_fuc):
    def cal_time():
        a = time.time()
        some_fuc()
        print('一共用时' + str(time.time() - a))
    return cal_time
@countime
def printsome(a):
    print('hello')
    return a
if __name__ == "__main__":
    s = printsome("She's smoking hot")
    print(s)
    
# output:报错,这个错特别的有意思,cal_time没有容纳参数的位置
TypeError: cal_time() takes 0 positional arguments but 1 was given
#很神奇吧,我以为会产生的错误是语法糖调用some_fuc()的时候告诉你没有参数,但是竟然是cal_time()需要接受参数。而参数,正式我们输入的"She's smoking hot"。那么传参就好解决了
# 一个可以Success的代码
def countime(some_fuc):
    def cal_time(*args):
        a = time.time()
        some_fuc(*args)
        print('一共用时' + str(time.time() - a))
    return cal_time
@countime
def printsome(a):
    print('hello')
    return a
if __name__ == "__main__":
    s = printsome("She's smoking hot")
    print(s)

拓展 *args 与 **kwards

  • *args 传递一个可变的参数传给实参。因为可变的灵活性应用于各种函数中

    同时,如果我们打印, 我们就会发现 args是以tuple(元组)的格式存在的。(当然打印的时候就不需要加*)

  • **kwargs 与 *args一样是一个可变的传参参数。不同的是kwargs是接受形如fuc(a = 1,b= 2), 这样赋值的参数的不定长传参事务

    **kwargs是以字典的形式存储参数

  • 参数名是否一定要用 args与 kwargs? 不是的 只要是*开头变量就可以充当args, **开头的变量就可以充当kwards (这个我已经在自己的代码跑过了), 但是这连个名字在各种框架甚至已经成为规范的程度,如无必要推荐还是直接写args与kwards,要为同伴着想嘛。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值