装饰器,顾名思义,是用来装饰某样东西的。那么它是用来装饰什么东东的呢?答案是函数。总结一下,装饰器就是修改其他函数某些功能的函数。
接下来,让我们一步一步来编写一个装饰器。
一、什么是函数
#在这里myfunc是一个变量,也是一个函数。二myfunc()则是一个函数调用
def myfunc(args = "Python"):
return "Hello " + args
myval = myfunc#声明一个变量将myfunc赋值给这个新变量,实际上myfunc是函数的地址(注意当前没有小括号),myval变量保存的是myfunc函数的地址
print(myval,myfunc)#当前输出的内容是相同的,即myfunc函数的地址
del myfunc#将原来的myfunc变量删除,之后myfunc变量将不可见,也就不能再调用myfunc函数了
print(myval,myval())#输出myval变量的值,并调用该函数
print(myfunc)#会报错,因为该变量已被删除
print(myfunc())#如果没有上面的输出语句,同样会报错,因为myfunc变量被删除,也就不涉及到调用myfunc函数了
输出结果如下:
二、在函数中定义函数
def myfunc(args = "Python"):
print("Hello "+args)
def insideFunc():
return "i am the inside function"
print(insideFunc())
print("now i am the outside function")
myfunc()
insideFunc()
输出结果如下:
三、从函数中返回函数
def myfunc(args = "Python"):
print("Hello "+args)
def insideFunc():
return "i am the inside function"
print(insideFunc())
print("now i am the outside function")
return insideFunc;#注意:该出返回没有小括号,否则,该处将是返回insideFunc函数执行后的结果
func = myfunc();
print(func,func())
输出结果如下:
四、将函数作为参数传递给另一个函数
def myfunc(args = "Python"):
print("Hello "+args)
return "funny"
def doSomething(func):
print("now ,in function doSomething")
print(func())
doSomething(myfunc)
输出结果如下:
五、应用以上只是,编写第一个装饰器函数
def myDecrator(func):
def insideFunc():
print("before func out")
func()
print("after func out")
return insideFunc;
def myfunc(args = "python"):
print("Hello "+args)
myfunc = myDecrator(myfunc)
myfunc()
输出结果如下:
六、使用@来运行之前的代码
def myDecrator(func):
def insideFunc():
print("before func out")
func()
print("after func out")
return insideFunc;
@myDecrator
def myfunc(args = "python"):
print("Hello "+args)
myfunc()
print("Over")
输出结果如下:
七、存在的小问题
def myDecrator(func):
def insideFunc():
print("before func out")
func()
print("after func out")
return insideFunc;
@myDecrator
def myfunc(args = "python"):
print("Hello "+args)
myfunc()
print(myfunc.__name__)
当我们试图输出myfunc.__name__时,输出的名称为insideFunc,如下:
这里的函数myfunc被insideFunc替代了。它重写了我们函数的名字和注释文档。幸运的是Python提供给我们一个简单的函数来解决这个问题,那就是functools.wraps。我们修改上一个例子来使用functools.wraps:
from functools import wraps
def myDecrator(func):
@wraps(func)
def insideFunc():
print("before func out")
func()
print("after func out")
return insideFunc;
@myDecrator
def myfunc(args = "python"):
print("Hello "+args)
myfunc()
print(myfunc.__name__)
输出结果如下:
现在看起来舒服多了。
八、蓝本规范
from functools import wraps
def decorator_name(f):
@wraps(f)
def decorated(*args, **kwargs):
if not can_run:
return "Function will not run"
return f(*args, **kwargs)
return decorated
@decorator_name
def func():
return("Function is running")
can_run = True
print(func())
# Output: Function is running
can_run = False
print(func())
# Output: Function will not run
注意:@wraps接受一个函数来进行装饰,并加入了复制函数名称、注释文档、参数列表等等的功能。这可以让我们在装饰器里面访问在装饰之前的函数的属性。
使用场景
现在我们来看一下装饰器在哪些地方特别耀眼,以及使用它可以让一些事情管理起来变得更简单。
授权(Authorization)
装饰器能有助于检查某个人是否被授权去使用一个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
日志(Logging)
日志是装饰器运用的另一个亮点。这是个例子:
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
这个实现有一个附加优势,在于比嵌套函数的方式更加整洁,而且包裹一个函数还是使用跟以前一样的语法:
@logit()
def myfunc1():
pass
现在,我们给 logit 创建子类,来添加 email 的功能(虽然 email 这个话题不会在这里展开)。
class email_logit(logit):
'''
一个logit的实现版本,可以在函数调用时发送email给管理员
'''
def __init__(self, email='admin@myproject.com', *args, **kwargs):
self.email = email
super(email_logit, self).__init__(*args, **kwargs)
def notify(self):
# 发送一封email到self.email
# 这里就不做实现了
pass
注:以上内容参考自https://www.runoob.com/w3cnote/python-func-decorators.html