先说概念,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,要为同伴着想嘛。