你学习python的时候一定也遇到了装饰器,但是很多学习教程对装饰器的解释都是绕来绕去的,把初学者都给绕的云里雾里,我想读者你一定有类似的经历对吧?不过不要着急,只要你仔细用心的观看我这篇文章之后,你对python装饰器就完全理解了。相信我,我是想将复杂的问题简单的讲解出来,分享给更多的人,如果还有什么问题,可以留言告诉我,我们一起进步。
本文主要能够回答以下问题。
装饰器,其实就是将一个函数作为参数传递给另外一个函数,先来看一段代码
def before_hi(func):
print("before executing hi()")
func()
def hi():
print("hi changpzh!")
before_hi(hi)
#outputs:
# before executing hi()
# hi changpzh!
解释:
其实这段代码对大多数人来说应该都是能轻易的看的懂的。
1. 首先定义了两个函数,一个是hi,一个是before_hi,before_hi接受一个函数作为参数。
2. 所以执行before_hi(hi),意思就是将hi这个函数作为参数传递给了before_hi
3. 因此打印会是先before hi(),然后是hi
但是,他和我们的装饰器有什么关系呢?别慌,听我慢慢道来
我先把上面的函数做简单的改造, before_hi函数做了一层封装,此时返回的就是一个封装后的函数wrap_function了。
def before_hi(a_func):
def wrap_function():
print("before executing hi()")
a_func()
return wrap_function
def hi():
print("hi changpzh!")
hi = before_hi(hi)
hi()
# outputs:
# before executing hi()
# hi changpzh!
解释:
1. 首先当hi = before_hi(hi)执行的时候,实际上是将hi这个函数作为参数传递给了before_hi,然后在before_hi中运行,before_hi此时返回一个wrap_function回去。
2. hi()相当于在运行wrap_function()了。然后进行了对应的打印
当然此时看起来还是和我们的装饰器没有一点关系,不过再看我对上面代码进行一些变化,以便让你真正的理解装饰器。我用装饰器来代替其中一行代码用`@before_hi 代替 hi = before_hi(hi)`,看以下代码
def before_hi(a_func):
def wrap_function():
print("before executing hi()")
a_func()
return wrap_function
@before_hi # 注意位置 等价于 hi = before_hi(hi)
def hi():
print("hi changpzh!")
hi()
# outputs:
# before executing hi()
# hi changpzh!
解释:
这里代码和之前代码只有很小的区别,就是 @before_hi 代替 hi = before_hi(hi)。但是执行之后得到的效果却是一样的。
说明装饰器@的作用,就是将函数作为参数传递给装饰器函数。
也就是说在这里 @before_hi 等价 hi = before_hi(hi),函数hi就被wrap_function代替了。
什么是装饰器?
就是将一个函数作为参数传递给另一个函数,就是装饰器。
我们再来进一步了解装饰器,通过hi.__name__可以看到输出结果为wrap_function,参看下图,这里的hi函数名,被wrap_function代替了,这不是我们想要的,我们想要的是hi.__name__输出为hi。
那么我们怎么办呢? 不慌,好在python提供了一个简单的函数来解决这个问题,那就是functiontools.wraps,我们修改上面例子,通过使用functiontools.wraps,代码如下
from functools import wraps
def before_hi(a_func):
@wraps(a_func)
def wrap_function():
print("before executing hi()")
a_func()
return wrap_function
@before_hi
def hi():
print("hi changpzh!")
# hi()
print(hi.__name__)
# outputs:
# hi
此时的hi.__name__就是hi了,看下图调试结果。
当然如果不用functiontools.wraps,还会改变函数的注释文档,所以建议用装饰器时,使用functiontools.wraps
在不改变原函数的情况下,可以对原函数的功能进行扩展。比如授权,日志等。
举例,我想在每次需要打印的时候,都能像下面一样在打印的字体外面包装两行*号。
代码
from functools import wraps
def print_star(func):
@wraps(func)
def star_print(*args, **kwargs):
print("*" * 50)
func(*args, **kwargs)
print("*" * 50)
return star_print
@print_star
def hello(name):
print(f"{name},欢迎来到装饰件世界")
hello('changpzh')
装饰器能有助于检查某个⼈是否被授权去使⽤⼀个web应⽤的端点(endpoint)。它们被⼤量
使⽤于Flask和Django web框架中。这⾥是⼀个例⼦来使⽤基于装饰器的授权
from functools import wraps
def requires_auth(func):
@wraps(func)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
authenticate()
return func(*args, **kwargs)
return decorated
⽇志是装饰器运⽤的另⼀个亮点,举例
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" + "args is" + str(args) + " kwargs is " + str(kwargs)
with open(logfile, mode='a') as opened_file:
opened_file.write(log_string + '\n')
return wrapped_function
return logging_decorator
@logit()
def func1():
print("test")
@logit('func2.log')
def func2():
pass
func1("first", 2, age=30, name="changpzh")
func2()
解释:
注意这里的装饰器使用,和之前的有很大的一个差异,在于,之前使用是@logit,但是这里是@logit(),这里多了一个括号,意味着代码运行到这里的时候,实际上是执行了一次函数logit的。
所以:代码运行过程中
1. 当运行到@logit()时,首先执行logit函数,返回了logging_decorator这个装饰器函数
2. 因此,在运行接下来的函数def func1()时,执行了logging_decorator(func),并且返回wrapped_function给func1. 此时func1就等价为wrapped_function了。
3. 运行func1时,就是运行wrapped_function,传入的参数就会给到*args, **kwargs。结果如下所示。
4. 同理运行func2
from datetime import datetime
class Logit(object):
def __init__(self, logfile='out.log'):
self.logfile = logfile
def __call__(self, func): # 在函数定义的时候调用
# 打印日志
self.print_log(func)
return func
def print_log(self, func):
log_string = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f ") + func.__name__ + " was called"
print(log_string)
# 打开logfile并写⼊
with open(self.logfile, 'a') as opened_file:
# 现在将⽇志打到指定的⽂件
opened_file.write(log_string + '\n')
@Logit() # 这里会调用 __init__
def func1(): # 这里会调用 __call__
print(f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f ")} func1 was really called')
pass
@Logit('func2.log')
def func2():
print(f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f ")} func2 was really called')
pass
func1()
func2()
运行结果如下所示:
好了,今天的装饰器就讲解到这里,相信你看完这篇文章之后,对装饰的运行原理应该很清楚了。再次遇到装饰器时,你也有足够的信心相信自己可以征服她了。