Python:关于装饰器
2017-12-23 09:31
337 查看
目的
AOP编程,解耦。一是为了少写代码,二是为了解耦。
为了把重点放在装饰器上,本文的代码没有引入很多包。大概要引入datetime,math等。
例子
问题提出
比如,需要添加一些通用的动作,又不想在原有的函数里面修改。e.g. 要在函数执行前打印出执行的时间(相当于写日志)。
def say(): print('hi')
最土的解决办法
def say(): time1 = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') print('Begin @ %s' % time1) print('hi')
缺点至少有2个。
1. 直接改变了say函数的业务。
2. 若有N个say函数,会有N段重复的代码。
少写代码但流程诡异的解决办法
想个办法,把say函数指针(C里面的说法)当做变量传给一个写日志的函数。像这样。def logger(func): time1 = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') print('Begin @ %s'%time1) func()而say函数保持不动。
def say(): print('hi')在main流程中调用时这么写。
logger(say)即可。
又有个问题。
在main流程中,明明是想执行say。
希望看见的是say(),这里却写的是logger(say)。
非常诡异。其实不想看见调用logger,而是想看见调用say。
自制装饰器(原生态)
有以下办法可以让main里面看见是say在调用,而不是logger在调用。def logger(func):
time1 = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print('Begin @ %s'%time1)
return func
def say(): print('hi')
say2 = logger(say)
say2()
在调用logger(say)时,返回了say函数本身的地址。然而在调用它之前,执行了输出当前时刻的函数。
say2整个函数相当于先输出时间,再执行say函数。这和预期一致。
当然,把变量say2的名字改成say也行,它只是一个名字而已,但其实这样做变量替换,看起来不太好。
就像数学 f(t) 做变量替换时,会写令 t' = t+1,不会写 t = t+1。t只是个哑指标。写成 t = t+1,你都不知道哪个t是哪个t。
Python提供的装饰器
自制一个装饰器是,难免会看见 say2 = logger(say) 这种我们对say函数改造的话。但其实在main流程中,我们并不想看见。
通过装饰器注解(注解也许是Java里面的话),可以避免这样的情况。
def logger(func):解释如下。
def wrapper():
time1 = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print('Begin @ %s'%time1)
func()
return wrapper
@logger
def say(): print('hi')
say()
@logger
def say()
的作用,相当于say = logger(say),左边的say已经不仅是print('hi')的say。正如上面所说的,写成say2 = logger(say)更好。
调用logger(say)的时候,把say函数的指针传给了logger作为输入参数,在logger的定义的wrapper函数作为返回参数。
所以 say2 = logger(say) 左边的say2其实是个函数,之后再调用say2,写为say2()。
现在的say2变成了wrapper函数。第一步输出时间,第二部执行say函数。
在wrapper里面,func()这句话就是在调用(call)函数,而被调用的函数正是作为logger输入参数的say函数。
所以wrapper里面调用的func()其实是只会pritn('hi')的say函数。
至于为什么要写成@logger的样子,以及为什么就相当于say = logger(say)了,这是语法规定了。这门语言就是这么设计的。
被装饰的函数有输入参数
def logger(func): def wrapper(name): time1 = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') print('Begin @ %s' % time1) func(name) return wrapper @logger def say(name): print('hi, %s!' % name) say('qcy')
@logger def say(name),相当于say2 = logger(say),那其实say2就是wrapper函数。
wrapper需要一个输入参数name,和原来的say所需要的是一样的。
而在wrapper里面执行的func(name),也就把name穿进去了,相当于在调用原生态(没加注解)的say函数,say(name)。
更通用的可变输入参数的写法
对于可变参数,更喜欢写成 f(*args, **kwargs)。所以装饰器里面的wrapper更习惯写成wrapper(*args, **kwargs)。def logger(func): def wrapper(*args, **kwargs): time1 = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') print('Begin @ %s' % time1) func(*args, **kwargs) return wrapper @logger def say(name): print('hi, %s!' % name) say('qcy')
被装饰的函数有返回参数
那就在wrapper里面再返回一个返回值就好。一个例子,计算算术平方根。要引入math。
def logger(func): def wrapper(*args, **kwargs): time1 = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') print('Begin @ %s' % time1) res = func(*args, **kwargs) return res return wrapper @logger def calc_sqrt(x): print('This is a function to calculate sqrt.') res = math.sqrt(x) return res result = calc_sqrt(81) print(result)
整个例子的输出结果是:
Begin @ 2017-12-23 10:10:00
This is a function to calculate sqrt.
9.0
多看看也就这么回事。
装饰器本身需要输入参数
装饰器本身就要参数,如有很多logger,但此时需要知道是哪一个logger在输出。import math import time def logger(logger_name): def decorator(func): def wrapper(*args, **kwargs): time1 = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') print('[%s] Begin @ %s' % (logger_name, time1)) return func(*args, **kwargs) return wrapper return decorator @logger('calculator_log') def calc_sqrt(x): print('This is a function to calculate sqrt.') res = math.sqrt(x) return res result = calc_sqrt(81) print(result)
被装饰以后,大概与以下几条命令相似。
calc_sqrt2 = logger('calculator_log') --> 函数calc_sqrt2变成了decorator
calc_sqrt3 = calc_sqrt2(calc_sqrt) --> calc_sqrt3 = decorator(calc_sqrt), 函数calc_sqrt3变成了wrapper
最后,计算calc_sqrt3(81) --> wrapper(81)
总结
引入functools,是为了把原生态的say函数的属性赋给wrapper。这是一个完整的装饰器。
import functools def log(func): @functools.wraps(func) def wrapper(*args, **kw): print 'call %s():' % func.__name__ return func(*args, **kw) return wrapper
这是一个带有输入参数的装饰器
import functools def log(text): def decorator(func): @functools.wraps(func) def wrapper(*args, **kw): print '%s %s():' % (text, func.__name__) return func(*args, **kw) return wrapper return decorator
装饰器的应用
装饰器通常用在一些通用的功能上,简化代码。如,写日志、安全验证、异常处理、传入参数等。
异常捕获及处理的例子
# -*- coding: utf-8 -*- import functools def send_email(email_info): print('send_email: 这里开始发邮件') print('内容: %s' % email_info['content']) print('...') def exception_handler(func): @functools.wraps(func) def wrapper(*args, **kwargs): print('函数执行前的一些操作') try: return func(*args, **kwargs) except Exception as e: print(e) print('函数执行失败,发送邮件提醒等...') send_email({'subject':'异常', 'content': e}) print('函数执行后的一些操作') return wrapper @exception_handler def say(): print('你好!') print('===现在要故意造一个错误') print1 say()
相关文章推荐
- 关于python的装饰器简单实用
- python中关于装饰器的理解
- 关于PYTHON的反射,装饰的练习
- [python基础]关于装饰器
- 关于Python中闭包与装饰器的理解
- python中闭包和装饰器的理解(关于python中闭包和装饰器解释最好的文章)。
- 关于python的装饰器
- 0基础学Python(6) —— 关于装饰器
- 关于Python的装饰器 decorator
- 关于python的装饰器
- <转载> 关于python的装饰器
- 简单说明Python中的装饰器的用法
- Python中的装饰器用法详解
- Python 学习笔记 装饰器 与 context
- 关于python无法安装Scrapy解决方案
- 关于Python中的yield
- python中使用xmltodic处理xml文件,关于其中的列表问题。
- ROS:关于tf的探索(4)Learning about tf and time(Python)
- python语言学习——关于切片
- Python 装饰器 decorator