装饰器,顾名思义,是用来装饰某样东西的。那么它是用来装饰什么东东的呢?答案是函数。总结一下,装饰器就是修改其他函数某些功能的函数。
接下来,让我们一步一步来编写一个装饰器。
一、什么是函数
#在这里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 + ' ') 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 + ' ') # 现在,发送一个通知 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