python装饰器

一、概述

python中可以看到@statement的语法,它通常出现在函数定义或类定义的前面一行。这就是python装饰器,用于在不做代码变动的情况下增强函数或类的功能。装饰器可以提取大量函数中与本身功能无关的类似代码,从而达到代码重用的目的。它有着很多经典使用场景,例如插入日志、性能测试、事务处理等等。

二、语法

装饰器的一般的语法如下:

#声明两个装饰器f1、f2
def f1(arg):
    def decorator(func):
        def wrapper(*args, **kw):
            # do something with 'arg'
            # do something
            return func(*args, **kw)
        return wrapper
    return decorator

def f2(func):
    def wrapper(*args, **kw):
        # do something
        return func(*args, **kw)
    return wrapper

#装饰器使用  
@f1(arg)
@f2
def func(): pass

## 上面的示例roughly equivalent to
def func(): pass
func = f1(arg)(f2(func))

上面展示了一个装饰器的使用示例(虽然没有实质含义):f1是一个带参数的装饰器,f2是一个普通装饰器(分类详解部分会进行介绍),一个函数可以有多个装饰器,通过嵌套方式执行(靠近原函数的装饰器先执行)。可以看出,本质上,decorator就是一个返回函数的高阶函数,通过@语法(语法糖)进行了简化,即@语法代替执行了func = f1(arg)(f2(func))

三、分类详解

装饰器根据应用对象,可以分为装饰函数的装饰器和装饰类的装饰器,在这两类下还可以具体细分:

(一)装饰函数的装饰器

我们一般常用到的是装饰函数的装饰器,它又可以细分为三种:普通装饰器、带参数的装饰器、类装饰器。

1.普通装饰器

即上文中的f2。它将真正执行的业务函数func包裹在内部的wrapper之中,并对这个wrapper进行增强(可以增加动作)然后返回,看起来func像是被f2装饰了。此例中,函数进入和退出时,被称为一个横切面,这种编程方式也被称为面向切面的编程(AOP)。

2.带参数的装饰器

即上文中的f1。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。python可以将参数传递到装饰器的环境中,增加了装饰器的灵活性。

3.类装饰器

相比函数装饰器,类装饰器灵活度更大,且具有高内聚、封装性等优点。具体示例如下:

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 bar():
    print ('bar')

bar()
'''result:
class decorator runing
bar
class decorator ending
'''

(二)装饰类的装饰器

装饰类的装饰器概念大致与装饰函数的装饰器相同,只是应用对象变为了类。python文档中提到装饰类的装饰器是“less commonly used”,即较少用到,所以笔者也没有进行深究,如果有需要可以进一步详细了解。

四、注意事项

虽然使用装饰器可以复用代码,动态增强函数功能,但是它也存在着一个问题。以语法部分的示例为例,func先后被f2和f1装饰,这个时候我们print(func.__name__)发现结果为wrapper,这是因为@语法等价于func = f1(arg)(f2(func)),原函数的元数据(__docstring__、参数列表等)已经被装饰器所返回的函数所覆盖,这种情况可能会影响到其他函数调用原函数的元数据。
解决方案是使用functools.wraps,它是一个内置装饰器,可以将原函数的元数据拷贝到装饰器函数之中。具体方法为在上例中的两行def wrapper(*args, **kw):代码的上面一行加上@wraps(func)即可。

五、实例

python文档中的实例

@staticmethod、@classmethod和@property。

一个具体问题

以下是一段斐波那契数列求和的递归解决方案:

def fibonacci(n):
    if n in [0, 1]:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

虽然很简洁,但是我们可以发现它的效率并不高,因为每一次递归都会重复计算以前计算出的结果,譬如本次n-1的结果已经在上次n-2时计算过了。为了提高这个函数的效率,我们可以增设一个缓存区,将计算结果存储下来,使得每次递归时可以直接使用以前计算出的结果而不需要重复计算。
装饰器可以在不改变原函数的情况下,提供一个优雅的解决方案:

def memory(f):
    cache = {}
    def wrapper(k):
        v = cache.get(k)
        if v is None:
            v = cache[k] = f(k)
        return v
    return wrapper

@memory增加至原函数声明前,则等价于fibonacci = memory(fibonacci),即可很好地解决这个问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值