相信我们或多或少都见过python中的装饰器,如:
@property
@wrap(func)
下面就讲解一下python中装饰器的作用!
首先记住装饰器的作用:
在运行函数之前会运行这个函数前的装饰器,然后起到改变函数的功能的作用!
def my_decorator(my_func):
def wrap_myfunc():
my_func()
print('我睡醒了') #这里我们改变了原来函数的功能
return wrap_myfunc
def my_func():
print('我在睡觉')
my_func = my_decorator(my_func)
my_func()
比如如上面代码所示,我有一个函数my_func的功能是打印我在睡觉,但现在我想改变我们函数的功能使我们睡醒,于是我们写了个my_decorator函数,将我们的my_func函数作为参数传进去,然后在内层的wrap_myfunc函数中打印我睡醒了!然后我们重新调用my_func()发现结果为:
可以看见,这么操作确实改变了my_func()的功能,这其实就是装饰器的原理,只不过我们并没有用@关键字而是自己手搓了出来。
那么原理是什么呢?
my_func = my_decorator(my_func)这一行因为my_decorator函数返回的是wrap_myfunc,所以其实就是my_func=wrap_myfunc,所以可以看出其实是狸猫换太子将我们原来的my_func函数换成wrap_myfunc了!
那么我们把上面的代码换成用正式的装饰器来实现就是:
def my_decorator(my_func):
def wrap_myfunc():
my_func()
print('我睡醒了') #这里我们改变了原来函数的功能
return wrap_myfunc
@my_decorator #由结果可以看到在我们想要改变的函数前加一个@引导的装饰器 = 我们上面的传递函数返回函数 my_func = my_decorator(my_func)
def my_func():
print('我在睡觉')
my_func()
这里我们在my_func()函数前面加上@my_decorator就等价于my_func = my_decorator(my_func)这一手狸猫换太子!输出结果:
值得一提的是,我们这手狸猫换太子直接将函数名与注释文档换了,虽然我们调用的还是my_func()函数,但是他的名字其实已经不叫my_func了(没想到吧,底层的名字才是真正的名字):
def my_decorator(my_func):
def wrap_myfunc():
my_func()
print('我睡醒了') #这里我们改变了原来函数的功能
return wrap_myfunc
@my_decorator #由结果可以看到在我们想要改变的函数前加一个@引导的装饰器 = 我们上面的传递函数返回函数 my_func = my_decorator(my_func)
def my_func():
print('我在睡觉')
my_func()
print(my_func.__name__) # output: wrap_myfunc,这不是我们想要的,可见直接这么加装饰器把我们原来函数的名字和注释文档重写了!
输出:
这显然不是我们想要的,我们只是想改变一下它的功能而已,并没有说要改变它的名字啊!
我们可以通过from functools import wraps导入这个包并且在我们要改变函数功能的那个函数前加上@wraps(my_func)这个装饰器并将我们不想改变名字的函数传进去就不会发生上面那种情况了!
from functools import wraps
def my_decorator(my_func):
@wraps(my_func) #在我们的装饰器里再加上这么一个装饰器就好了!
def wrap_myfunc():
my_func()
print('我睡醒了')
return wrap_myfunc
@my_decorator
def my_func():
print('我在睡觉')
my_func()
print(my_func.__name__) # output: my_func
运行结果:
上面只是最简单的一种情况,因为我们的函数并没有参数与返回值,下面我们稍微加点难度,让函数带上参数与返回值,希望不会被绕晕!
from functools import wraps
def logit(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logit
def addition_func(x):
"""Do some math."""
return x + x
result = addition_func(4)
上面这段代码的作用是:我们原来的addtion_func函数的功能只是返回原来参数的两倍,我们加上一个装饰器使得每次调用这个函数时都会先打印 (函数名) was called。你可能会想我自己在原来的函数里面加一个print语句自己打印不就行了?想的很好,但是如果当代码量一大,你有100个函数都需要这样干,那你就要打印一百次,这就比用装饰器慢了!这时我们装饰器的作用就体现出来了!
回到我们上面的代码:
我们发现与第一个案例比,这个案例主要函数多了返回值与参数。我们来分析一下:
调用装饰器等价于:addition_func = logit(addition_func),而logit函数返回的是with_logging,所以就相当于addition_func = with_logging!那么result = addtion_func(4)就相当于result = with_logging(4),而with_logging函数又返回的是func函数且他俩参数是一样的,所以最后就等价于返回addition_func(4)!所以result = 8!
运行结果:
结果与我们猜想的一样!
下面让我们再加把火,让我们看看带参数的装饰器!
from functools import wraps
def logit(logfile='out.log'):
def logging_decorator(func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
# 打开logfile,并写入内容
with open(logfile, 'a') as opened_file:
# 现在将日志打到指定的logfile
opened_file.write(log_string + '\n')
return func(*args, **kwargs)
return wrapped_function
return logging_decorator
@logit()
def myfunc1():
pass
myfunc1()
# Output: myfunc1 was called
# 现在一个叫做 out.log 的文件出现了,里面的内容就是上面的字符串
@logit(logfile='func2.log')
def myfunc2():
pass
myfunc2()
# Output: myfunc2 was called
# 现在一个叫做 func2.log 的文件出现了,里面的内容就是上面的字符串
可以明显的看到,@logit(logfile='func2.log')这一行的装饰器带上了参数!
但是我们发现,竟然又套了一层函数!希望别被绕晕!
还是一样,我们主要关心三个return。还是一样,加上装饰器就相当于myfunc2 = logit(my_func2)
而logit函数返回wrapped_funtion函数,所以就相当于myfunc2 = wrapped_function,所以运行myfunc1()就相当于运行wrapped_function(),虽然wrapped_function函数有返回值为func(),但是我们的my_func1()函数并没有返回值,所以不需要在运行my_func1()时接收返回值(可以自己将pass改为return 'ok',然后str = myfunc1(),再打印str就可以看到有返回值的情况)!
运行结果已经在注释里写了,好像也不是很绕?这个含参装饰器的功能就是创建一个自定义名字的日志文件将 (函数名) was recalled写入日志文件!