装饰器概述
装饰器就是在特定条件下为某些函数在不改动函数体的时候为函数新添加一些功能,可以对原函数进行功能扩展,而且还不需要修改原函数的内容,也不需要修改原函数的调用,装饰器的使用符合了面向对象编程的开放封闭原则,即对扩展开放、对修改封闭。
装饰器基于@语法和函数闭包,将原函数封装在闭包中,然后将函数赋值为一个新的函数(内置函数),执行函数时再在内层函数中执行闭包中的原函数。
装饰器使用
定义一个函数add,实现了两个数的相加。
def add():
x = 1
y = 2
z = x + y
print(z)
如果需要计算这些代码的执行时间,应该怎么办呢?一个可行的方法就是在函数体内部加上计算执行时间的代码。
import time
def add():
start = time.time()
x = 1
y = 2
z = x + y
print(z)
end = time.time()
print("total time:" + str(end - start))
add()
输出结果为:
3
total time:0.0
这样已经违背了开闭原则,如果不想在修改add函数的基础上,来计算该函数的运行时间,又该怎么办呢?我们知道,函数的参数传递的值可以为一个函数,基于这个特性,将源代码修改如下:
import time
def add():
x = 1
y = 2
z = x + y
print(z)
def count_time(function):
def wrap():
start = time.time()
function()
end = time.time()
print("total time:" + str(end - start))
return wrap
add = count_time(add)
add()
输出结果为:
3
total time:0.0
首先向count_time函数传递一个参数,这个参数的值是一个add函数;然后该count_time函数的返回值是一个wrap函数,这个wrap函数的函数体内包含了add函数,并在此基础上增加了计算时间的代码。这样,我们就在不修改add函数的基础上,实现了计算函数add的运行时间的功能。
但是这样又出现了一个问题,我们每次在调用add函数前,都需要调用一次count_time函数。这时候应该如何解决呢?
如果看过其他python项目里面的代码里,难免会看到@符号,这个@符号就是装饰器的语法糖。因此上面简单的装饰器还是可以通过语法糖来实现的,这样就可以省去add = count_time(add)这一行代码。
import time
def count_time(function):
def wrap():
start = time.time()
function()
end = time.time()
print("total time:" + str(end - start))
return wrap
@count_time
def add():
x = 1
y = 2
z = x + y
print(z)
add()
输出结果为:
3
total time:0.0
上面我们用函数来实现了一个装饰器,如果用类来实现一个同样功能的装饰器应该怎么做呢?可以通过类的_ _ init _ _ 方法来传入一个add函数,然后通过 _ _ call _ _ 函数来添加相应的功能。
import time
class CountTime:
def __init__(self, function):
self.function = function
def __call__(self, *args, **kwargs):
start = time.time()
self.function(*args, **kwargs)
end = time.time()
print("total time:" + str(end - start))
@CountTime
def add():
x = 1
y = 2
z = x + y
print(z)
add()
输出结果为:
3
total time:0.0
装饰器传参
上面实现了一个简单的装饰器,这个装饰器其实就是一个函数,既然装饰器是一个函数,那么肯定就可以有参数。如何实现一个带有参数的装饰器呢?为了实现传参,我们可以在原函数的基础上,再加入一层函数。
def count_time_args(arg1):
def count_time(function):
def wrap():
start = time.time()
function()
end = time.time()
print("total time:" + str(end - start))
print(arg1)
return wrap
return count_time
@count_time_args(arg1="end")
def add():
x = 1
y = 2
z = x + y
add()
输出结果为:
3
total time:0.0
end
既然函数形式的装饰器能传参,那么类装饰器肯定也能传参,如何向类装饰器传入参数呢?如果带有参数的化,我们不能像函数那么在函数的外面再包装一层,而是将装饰器的参数传入_ _ init _ _ (),而将函数传入_ _ call _ _()。
import time
class CountTime:
def __init__(self, arg1):
self.arg1 = arg1
def __call__(self, function, *args, **kwargs):
def wrap():
start = time.time()
function(*args, **kwargs)
end = time.time()
print("total time:" + str(end - start))
print(self.arg1)
return wrap
@CountTime(arg1="end")
def add():
x = 1
y = 2
z = x + y
print(z)
add()
输出结果为:
3
total time:0.0
end
装饰器顺序
如果一个函数同时被多个装饰器所修饰时,这些装饰器的执行顺序是什么样的呢?
def decorator_1(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
print('我是装饰器1')
return wrapper
def decorator_2(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
print('我是装饰器2')
return wrapper
def decorator_3(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
print('我是装饰器3')
return wrapper
@decorator_1
@decorator_2
@decorator_3
def main():
print("this is a text")
main()
结果为
this is a text
我是装饰器3
我是装饰器2
我是装饰器1
从结果中可以看出,如果一个函数被多个装饰器修饰,那么多个装饰器会按照由内向外的顺序生效。
|