python装饰器

python学习笔记之装饰器

装饰器

先来看一个例子:

1
2
3
4
5
6
7
8
9
import time

def timeit(fn):
start = time.time()
fn()
print(time.time() - start)

def sleep():
time.sleep(3)

上边代码定义了两个函数,timeit函数能够模拟计算出在执行fn函数所花费的时间,调用如下:

1
timeit(sleep)
3.003638505935669

这样来计算一个函数的执行时间是有缺陷的,sleep函数必须是一个无参的函数,那怎样才能让sleep能接收参数呢?做如下改进:

1
2
3
4
5
6
7
8
9
def timeit_1(fn):
def wrap(x):
start = time.time()
fn(x)
print(time.time() - start)
return wrap

def sleep_1(x):
time.sleep(x)

调用方式如下:

1
timeit_1(sleep_1)(3)
3.0035746097564697

这样timeit_1函数通过wrap函数进行一次包装后就可以让sleep函数接收一个参数,但如果sleep函数所接收的参数个数是不确定的呢?这个可以采用python中的可变参数来解决,如下:

1
2
3
4
5
6
def timeit_2(fn):
def wrap(*args,**kwargs):
start = time.time()
fn(*args,**kwargs)
print(time.time() - start)
return wrap

这样对于sleep这个函数所需要接收的参数个数就没有限制了,调用方法与上边的不变:

1
timeit_2(sleep_1)(3)
3.003852605819702
1
timeit_2(sleep_1)(x=3)   #以关键字参数方式传递参数
3.003178119659424

其实这里的timeit_2就是一个装饰器,在python中有一个语法糖来表示,如果在执行一个函数时,比如上边的sleep_1函数想在其执行前后增加一些语句操作,比如上边的start = time.time()print(time.time() - start),那在定义sleep_1函数时就可以加上一个装饰器来装饰此函数,这样定义的函数有其独特的语法,在定义函数时在其上边用一个@符号加上装饰器函数的名称即可,如下:

1
2
3
@timeit_2
def sleep_2(x):
time.sleep(x)

如上定义后的sleep_2的函数,我们在调用时就不需要再去调用timeit_2这个函数了,直接调用sleep_2函数即可,如下:

1
sleep_2(3)
3.0038673877716064

上边调用sleep_2(3)时,函数的执行流程是怎样的呢?

  1. 首先把sleep_2函数作为参数传递到timeit_2这个装饰器函数中执行,返回一个wrap函数对象

  2. 再把调用sleep_2(3)函数时的参数3传递到wrap函数参数中进行函数调用,实质就是解释器会转换成timeit_2(sleep_2)(3)的方式来调用,但是在已经使用@timeit_2语法来装饰函数sleep_2的场景下不能再使用timeit_2(sleep_2)(3)来调用函数,因为这样wrap函数会被执行两次。

最后总结一下:

装饰器的本质就是一个函数,此函数接收一个函数作为参数,返回一个函数,通常,返回的这个函数,是对传入的函数执行进行前后增加了一些语句,所以叫做装饰器。

文章目录
  1. 1. 装饰器
|