Python 装饰器使用详解
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象. 经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。 先来看一个简单例子: def now(): print('2017_7_29') 现在有一个新的需求,希望可以记录下函数的执行日志,于是在代码中添加日志代码: def now(): print('2017_7_29') logging.warn("running") 假设有类似的多个需求,怎么做?再写一个logging在now函数里?这样就造成大量雷同的代码,为了减少重复写代码,我们可以这样做,重新定义一个函数:专门处理日志 ,日志处理完之后再执行真正的业务代码. def use_logging(func): logging.warn("%s is running" % func.__name__) func() def now(): print('2017_7_29') use_logging(now) 在实现,逻辑上不难, 但是这样的话,我们每次都要将一个函数作为参数传递给日志函数。而且这种方式已经破坏了原有的代码逻辑结构,之前执行业务逻辑时,执行运行now(),但是现在不得不改成use_logging(now)。那么有没有更好的方式的呢?当然有,答案就是装饰器。 首先要明白函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。例如: def now(): print('2017_7_28') f=now f() # 函数对象有一个__name__属性,可以拿到函数的名字 print('now.__name__:',now.__name__) print('f.__name__:',f.__name__) 简单装饰器 本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下: def log(func): def wrapper(*args,**kw): print('call %s():'%func.__name__) return func(*args,**kw) return wrapper # 由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在, 上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数.现在执行: now = log(now) now() 输出结果: 函数log就是装饰器,它把执行真正业务方法的func包裹在函数里面,看起来像now被log装饰了。在这个例子中,函数进入时 ,被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。 使用语法糖: @log def now(): print('2017_7_28') @符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作 这样我们就可以省去now = log(now)这一句了,直接调用now()即可得到想要的结果。如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性。 装饰器在Python使用如此方便都要归因于Python的函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。 带参数的装饰器: 如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会复杂一点。比如,要自定义log的文本: def log(text): def decorator(func): def wrapper(*args,**kw): print('%s %s()'%(text,func.__name__)) return func(*args,**kw) return wrapper return decorator 这个3层嵌套的decorator用法如下: @log('goal') def now(): print('2017-7-28') now() 等价于
# 首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数 因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的'now'变成了'wrapper': print(now.__name__) # wrapper 因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。 不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下: 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,**kw) return wrapper return decorator 类装饰器: 再来看看类装饰器,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器还可以依靠类内部的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法 import time class Foo(object): def __init__(self,func): self._func = func def __call__(self): print ('class decorator runing') self._func() print ('class decorator ending') @Foo def now(): print (time.strftime('%Y-%m-%d',time.localtime(time.time()))) now() 总结: 概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。 同时在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |