原文:http://docs.pythontab.com/interpy/decorators/your_first_decorator/
你的第一个装饰器:
在上一个例子里,其实我们已经创建了一个装饰器!现在我们修改下上一个装饰器,并编写一个稍微更有用点的程序:
1 def a_new_decorator(a_func): 2 3 def wrapTheFunction(): 4 print("I am doing some boring work before executing a_func()") 5 6 a_func() 7 8 print("I am doing some boring work after executing a_func()") 9 10 return wrapTheFunction 11 12 def a_function_requiring_decoration(): 13 print("I am the function which needs some decoration to remove my foul smell") 14 15 a_function_requiring_decoration() 16 #outputs: "I am the function which needs some decoration to remove my foul smell" 17 18 a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration) 19 #now a_function_requiring_decoration is wrapped by wrapTheFunction() 20 21 a_function_requiring_decoration() 22 #outputs:I am doing some boring work before executing a_func() 23 # I am the function which needs some decoration to remove my foul smell 24 # I am doing some boring work after executing a_func()
你看明白了吗?我们刚刚应用了之前学习到的原理。这正是python中装饰器做的事情!它们封装一个函数,并且用这样或者那样的方式来修改它的行为。现在你也许疑惑,我们在代码里并没有使用@符号?那只是一个简短的方式来生成一个被装饰的函数。这里是我们如何使用@来运行之前的代码:
1 @a_new_decorator 2 def a_function_requiring_decoration(): 3 """Hey you! Decorate me!""" 4 print("I am the function which needs some decoration to " 5 "remove my foul smell") 6 7 a_function_requiring_decoration() 8 #outputs: I am doing some boring work before executing a_func() 9 # I am the function which needs some decoration to remove my foul smell 10 # I am doing some boring work after executing a_func() 11 12 #the @a_new_decorator is just a short way of saying: 13 a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
希望你现在对Python装饰器的工作原理有一个基本的理解。如果我们运行如下代码会存在一个问题:
1 print(a_function_requiring_decoration.__name__) 2 # Output: wrapTheFunction
这并不是我们想要的!Ouput输出应该是“a_function_requiring_decoration”。这里的函数被warpTheFunction替代了。它重写了我们函数的名字和注释文档(docstring)。幸运的是Python提供给我们一个简单的函数来解决这个问题,那就是functools.wraps。我们修改上一个例子来使用functools.wraps:
1 from functools import wraps 2 3 def a_new_decorator(a_func): 4 @wraps(a_func) 5 def wrapTheFunction(): 6 print("I am doing some boring work before executing a_func()") 7 a_func() 8 print("I am doing some boring work after executing a_func()") 9 return wrapTheFunction 10 11 @a_new_decorator 12 def a_function_requiring_decoration(): 13 """Hey yo! Decorate me!""" 14 print("I am the function which needs some decoration to " 15 "remove my foul smell") 16 17 print(a_function_requiring_decoration.__name__) 18 # Output: a_function_requiring_decoration
现在好多了。我们接下来学习装饰器的一些常用场景。
蓝本规范:
1 from functools import wraps 2 def decorator_name(f): 3 @wraps(f) 4 def decorated(*args, **kwargs): 5 if not can_run: 6 return "Function will not run" 7 return f(*args, **kwargs) 8 return decorated 9 10 @decorator_name 11 def func(): 12 return("Function is running") 13 14 can_run = True 15 print(func()) 16 # Output: Function is running 17 18 can_run = False 19 print(func()) 20 # Output: Function will not run
注意:@wraps接受一个函数来进行装饰,并加入了复制函数名称、注释文档、参数列表等等的功能。这可以让我们在装饰器里面访问在装饰之前的函数的属性。
使用场景
现在我们来看一下装饰器在哪些地方特别耀眼,以及使用它可以让一些事情管理起来变得更简单。
授权(Authorization)
装饰器能有助于检查某个人是否被授权去使用一个web应用的端点(endpoint)。它们被大量使用于Flask和Django web框架中。这里是一个例子来使用基于装饰器的授权:
1 from functools import wraps 2 3 def requires_auth(f): 4 @wraps(f) 5 def decorated(*args, **kwargs): 6 auth = request.authorization 7 if not auth or not check_auth(auth.username, auth.password): 8 authenticate() 9 return f(*args, **kwargs) 10 return decorated
日志(Logging)
日志(Logging)日志是装饰器运用的另一个亮点。这是个例子:
1 from functools import wraps 2 3 def logit(func): 4 @wraps(func) 5 def with_logging(*args, **kwargs): 6 print(func.__name__ + " was called") 7 return func(*args, **kwargs) 8 return with_logging 9 10 @logit 11 def addition_func(x): 12 """Do some math.""" 13 return x + x 14 15 16 result = addition_func(4) 17 # Output: addition_func was called
带参数的装饰器
来想想这个问题,难道@wraps
不也是个装饰器吗?但是,它接收一个参数,就像任何普通的函数能做的那样。那么,为什么我们不也那样做呢?
这是因为,当你使用@my_decorator
语法时,你是在应用一个以单个函数作为参数的一个包裹函数。记住,Python里每个东西都是一个对象,而且这包括函数!记住了这些,我们可以编写一下能返回一个包裹函数的函数。
在函数中嵌入装饰器
我们回到日志的例子,并创建一个包裹函数,能让我们指定一个用于输出的日志文件。
1 from functools import wraps 2 3 def logit(logfile='out.log'): 4 def logging_decorator(func): 5 @wraps(func) 6 def wrapped_function(*args, **kwargs): 7 log_string = func.__name__ + " was called" 8 print(log_string) 9 # 打开logfile,并写入内容 10 with open(logfile, 'a') as opened_file: 11 # 现在将日志打到指定的logfile 12 opened_file.write(log_string + ' ') 13 return func(*args, **kwargs) 14 return wrapped_function 15 return logging_decorator 16 17 @logit() 18 def myfunc1(): 19 pass 20 21 myfunc1() 22 # Output: myfunc1 was called 23 # 现在一个叫做 out.log 的文件出现了,里面的内容就是上面的字符串 24 25 @logit(logfile='func2.log') 26 def myfunc2(): 27 pass 28 29 myfunc2() 30 # Output: myfunc2 was called 31 # 现在一个叫做 func2.log 的文件出现了,里面的内容就是上面的字符串
装饰器类
现在我们有了能用于正式环境的logit
装饰器,但当我们的应用的某些部分还比较脆弱时,异常也许是需要更紧急关注的事情。比方说有时你只想打日志到一个文件。而有时你想把引起你注意的问题发送到一个email,同时也保留日志,留个记录。这是一个使用继承的场景,但目前为止我们只看到过用来构建装饰器的函数。
幸运的是,类也可以用来构建装饰器。那我们现在以一个类而不是一个函数的方式,来重新构建logit
。
1 from functools import wraps 2 3 class logit(object): 4 def __init__(self, logfile='out.log'): 5 self.logfile = logfile 6 7 def __call__(self, func): 8 @wraps(func) 9 def wrapped_function(*args, **kwargs): 10 log_string = func.__name__ + " was called" 11 print(log_string) 12 # 打开logfile并写入 13 with open(self.logfile, 'a') as opened_file: 14 # 现在将日志打到指定的文件 15 opened_file.write(log_string + ' ') 16 # 现在,发送一个通知 17 self.notify() 18 return func(*args, **kwargs) 19 return wrapped_function 20 21 def notify(self): 22 # logit只打日志,不做别的 23 pass
这个实现有一个附加优势,在于比嵌套函数的方式更加整洁,而且包裹一个函数还是使用跟以前一样的语法:
1 @logit() 2 def myfunc1(): 3 pass
现在,我们给logit
创建子类,来添加email的功能(虽然email这个话题不会在这里展开)。
1 class email_logit(logit): 2 ''' 3 一个logit的实现版本,可以在函数调用时发送email给管理员 4 ''' 5 def __init__(self, email='admin@myproject.com', *args, **kwargs): 6 self.email = email 7 super(logit, self).__init__(*args, **kwargs) 8 9 def notify(self): 10 # 发送一封email到self.email 11 # 这里就不做实现了 12 pass
从现在起,@email_logit
将会和@logit
产生同样的效果,但是在打日志的基础上,还会多发送一封邮件给管理员。