今天我们一起来学习python中的装饰器,要学习装饰器,我们首先要搞清楚函数嵌套和闭包相关的知识,如果对函数嵌套和闭包的知识搞清楚了的同学,可以直接阅读本文,如果对此还不清楚的,请阅读我的另一篇博文,【超详细解释】一文带你了解python中的函数嵌套和闭包。
好了,下面进入正文。
我们先来看看一个简单的容易理解的例子。
def hi():
return "hi yasoob!"
def doSomethingBeforeHi(func):
print("I am doing some boring work before executing hi()")
print(func())
doSomethingBeforeHi(hi)
# outputs:I am doing some boring work before executing hi()
# hi yasoob!
我们上面这个小例子就是在一个函数中接受另一个函数作为参数。然后执行传进来的函数。
def a_new_decorator(a_func):
def wrapTheFunction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after executing a_func()")
return wrapTheFunction
# a_new_decorator是一个嵌套函数,它接收一个函数作为变量,并且返回它的内嵌函数。
def a_function_requiring_decoration():#1
print("I am the function which needs some decoration to remove my foul smell")
# 这里定义了一个函数a_function_requiring_decoration
a_function_requiring_decoration()#2
# outputs: "I am the function which needs some decoration to remove my foul smell"
#这里是函数a_function_requiring_decoration的直接调用
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
# now a_function_requiring_decoration is wrapped by wrapTheFunction()
# 这里的a_function_requiring_decoration不是我们上面调用函数
#a_function_requiring_decoration了,这句话是调用函数a_new_decorator,并且这里函数的
#a_new_decorator的参数是函数#1,这里都是同名的函数,怕讲不清楚。好了,现在基本明白了,
#括号里面的是上面的函数,现在执行了函数a_new_decorator,它返回的是它的内嵌函数wrapTheFunction
a_function_requiring_decoration()
# outputs:I am doing some boring work before executing a_func()
# I am the function which needs some decoration to remove my foul smell
# I am doing some boring work after executing a_func()
#那现在我们知道了再次调用函数a_function_requiring_decoration,其实执行的是函数wrapTheFunction
#因为进过执行后,a_function_requiring_decoration指向了函数wrapTheFunction的内存地址
这个装饰器,你看明白了吗?
这正是 python 中装饰器做的事情!它们封装一个函数,并且用这样或者那样的方式来修改它的行为。现在你也许疑惑,我们在代码里并没有使用 @ 符号?那只是一个简短的方式来生成一个被装饰的函数。
下面,我们来改写上面的代码。
def a_new_decorator(a_func):
def wrapTheFunction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after executing a_func()")
return wrapTheFunction
@a_new_decorator
def a_function_requiring_decoration():
"""Hey you! Decorate me!"""
print("I am the function which needs some decoration to "
"remove my foul smell")
a_function_requiring_decoration()
# outputs: I am doing some boring work before executing a_func()
# I am the function which needs some decoration to remove my foul smell
# I am doing some boring work after executing a_func()
# the @a_new_decorator is just a short way of saying:
# a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
这两个例子的下面的语句就达到了同样的效果了。
a_function_requiring_decoration()
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
a_function_requiring_decoration()
现在我们有个问题:
来看下面的代码:
还是差不多的代码,只是打印a_function_requiring_decoration这个函数的名字。
def a_new_decorator(a_func):
def wrapTheFunction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after executing a_func()")
return wrapTheFunction
@a_new_decorator
def a_function_requiring_decoration():
"""Hey you! Decorate me!"""
print("I am the function which needs some decoration to "
"remove my foul smell")
a_function_requiring_decoration()
print(a_function_requiring_decoration.__name__)
#wrapTheFunction
Ouput输出应该是"a_function_requiring_decoration"。这里的函数被warpTheFunction替代了。它重写了我们函数的名字和注释文档(docstring)。
出乎意料的是打印出来的名字是wrapTheFunction,这并不是我们期望了,我们还是期望经过装饰后,我们的原函数还是原来的函数,而不是指向别的函数了,虽然我们的函数经过装饰后会执行一下装饰器里面的功能。
怎么办呢?
幸运的是Python提供给我们一个简单的函数来解决这个问题,那就是functools.wraps。我们修改上一个例子来使用functools.wraps:
我们来看修改后的代码:
from functools import wraps
def a_new_decorator(a_func):
@wraps(a_func)
def wrapTheFunction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after executing a_func()")
return wrapTheFunction
@a_new_decorator
def a_function_requiring_decoration():
"""Hey you! Decorate me!"""
print("I am the function which needs some decoration to "
"remove my foul smell")
a_function_requiring_decoration()
print(a_function_requiring_decoration.__name__)
# a_function_requiring_decoration
这样我们被装饰的函数还是指向的是原来的函数。
注意:@wraps接受一个函数来进行装饰,并加入了复制函数名称、注释文档、参数列表等等的功能。这可以让我们在装饰器里面访问在装饰之前的函数的属性。
装饰器能有助于检查某个人是否被授权去使用一个web应用的端点(endpoint)。它们被大量使用于Flask和Django web框架中。这里是一个例子来使用基于装饰器的授权:
from functools import wraps
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
authenticate()
return f(*args, **kwargs)
return decorated
日志是装饰器的应用的另一个亮点。
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)
# Output: addition_func was called
来想想这个问题,难道@wraps不也是个装饰器吗?但是,它接收一个参数,就像任何普通的函数能做的那样。那么,为什么我们不也那样做呢? 这是因为,当你使用@my_decorator语法时,你是在应用一个以单个函数作为参数的一个包裹函数。记住,Python里每个东西都是一个对象,而且这包括函数!记住了这些,我们可以编写一下能返回一个包裹函数的函数。
我们回到日志的例子,并创建一个包裹函数,能让我们指定一个用于输出的日志文件。
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装饰器,但当我们的应用的某些部分还比较脆弱时,异常也许是需要更紧急关注的事情。比方说有时你只想打日志到一个文件。而有时你想把引起你注意的问题发送到一个email,同时也保留日志,留个记录。这是一个使用继承的场景,但目前为止我们只看到过用来构建装饰器的函数。
幸运的是,类也可以用来构建装饰器。那我们现在以一个类而不是一个函数的方式,来重新构建logit。
from functools import wraps
class logit(object):
def __init__(self, logfile='out.log'):
self.logfile = logfile
def __call__(self, func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
# 打开logfile并写入
with open(self.logfile, 'a') as opened_file:
# 现在将日志打到指定的文件
opened_file.write(log_string + '\n')
# 现在,发送一个通知
self.notify()
return func(*args, **kwargs)
return wrapped_function
def notify(self):
# logit只打日志,不做别的
pass
这里用到的魔术方法__call__(),简单说一下,这个方法出现在类里面,就让这个类的实例能够像函数一样被调用,并且调用的方法就是__call__()。
这个实现有一个附加优势,在于比嵌套函数的方式更加整洁,而且包裹一个函数还是使用跟以前一样的语法:
@logit()
def myfunc1():
pass
class email_logit(logit):
'''
一个logit的实现版本,可以在函数调用时发送email给管理员
'''
def __init__(self, email='[email protected]', *args, **kwargs):
self.email = email
super(email_logit, self).__init__(*args, **kwargs)
def notify(self):
# 发送一封email到self.email
# 这里就不做实现了
pass
从现在起,@email_logit 将会和 @logit 产生同样的效果,但是在打日志的基础上,还会多发送一封邮件给管理员。
至此完结,希望你看完本文后对装饰器有一个初步的认识和应用。
本文参考菜鸟教程。https://www.runoob.com/w3cnote/python-func-decorators.html
关于装饰器的一些链接:
理解python装饰器看这一篇就够了