Python装饰器

Python装饰器

声明:文章内容转自www.cnblogs.com/wilber2013/p/4657155.html ,有局部自主修改,修改内容用红色标记,并添加部分自己测试用的代码辅助理解。

装饰模式有很多经典的使用场景,例如插入日志、性能测试、事务处理等等,有了装饰器,就可以提取大量函数中与本身功能无关的类似代码,从而达到代码重用的目的。下面就一步步看看Python中的装饰器。

一个简单的需求

现在有一个简单的函数"myfunc",想通过代码得到这个函数的大概执行时间。

我们可以直接把计时逻辑方法"myfunc"内部,但是这样的话,如果要给另一个函数计时,就需要重复计时的逻辑。所以比较好的做法是把计时逻辑放到另一个函数中("deco"),如下:

但是,上面的做法也有一个问题,就是所有的"myfunc"调用处都要改为"deco(myfunc)"。

下面,做一些改动,来避免计时功能对"myfunc"函数调用代码的影响:

经过了上面的改动后,一个比较完整的装饰器(deco)就实现了,装饰器没有影响原来的函数,以及函数调用的代码。例子中值得注意的地方是,Python中一切都是对象,函数也是,所以代码中改变了"myfunc"对应的函数对象。

装饰器语法糖

在Python中,可以使用"@"语法糖来精简装饰器的代码:

使用了"@"语法糖后,我们就不需要额外代码来给"myfunc"重新赋值了,其实"@deco"的本质就是"myfunc = deco(myfunc)",当认清了这一点后,后面看带参数的装饰器就简单了。

被装饰的函数带参数

前面的例子中,被装饰函数的本身是没有参数的,下面看一个被装饰函数有参数的例子:

从例子中可以看到,对于被装饰函数需要支持参数的情况,我们只要使装饰器的内嵌函数支持同样的签名即可。

也就是说这时,"addFunc(3, 8) = deco(addFunc(3, 8))"。

这里还有一个问题,如果多个函数拥有不同的参数形式,怎么共用同样的装饰器?在Python中,函数可以支持(*args, **kwargs)可变参数,所以装饰器可以通过可变参数形式来实现内嵌函数的签名

示例:

#-*-coding:utf-8-*-
import time

def deco(arg=True):
    if arg:
        def _deco(func):
            def wrapper(*args,**kwargs):##备注:(*args, **kwargs)是一个整体,代表任何可能的参数(任何个数,任何形式),与核心函数func后面的相同
                startTime=time.time()
                func(*args,**kwargs)   ##备注:(*args, **kwargs)是一个整体,代表任何可能的参数(任何个数,任何形式),与新函数wrapper后面的相同
                endTime=time.time()
                time_cost = (endTime-startTime)*1000
                print("deco added")
                print("time_cost = %f ms \n"%(time_cost))
            return wrapper
    else:
        def _deco(func):
            return func
    return _deco
@deco()
def myFunc():
    print('hello world')

@deco()
def myFunc2(a,b):
    print(a)
    print('hello  world')
    print(b)

@deco()
def myFunc3(a,b,c):
    print(a)
    print('hello  world')
    print(b,c)

if __name__=="__main__":
    myFunc()
    myFunc2(1, 2)
    myFunc3(1,2,3)

带参数的装饰器

装饰器本身也可以支持参数,例如说可以通过装饰器的参数来禁止计时功能:

通过例子可以看到,如果装饰器本身需要支持参数,那么装饰器就需要多一层的内嵌函数。

这时候,"addFunc(3, 8) = deco(True)( addFunc(3, 8))","myFunc() = deco(False)( myFunc ())"。


def deco(arg=True):                ##备注:此处可以不用显式输入func,装饰器语法糖会自动识别并添加临近的原函数为参数
    if arg:
        def _deco(func):           ##备注:此处需要带参数,参数为原函数
            def new_func(insert):  ##备注:此处需要带参数,参数为与原函数相同的参数
                print("deco0-0 added")
                func(insert)
                print("deco0-1 added")
            return new_func         ##备注:返回的函数,后面不用加(),也不用带参数
    else:
        def _deco(func):            ##备注:此处需要带参数,参数为原函数
            return func             ##备注:返回的函数,后面不用加(),也不用带参数
    return _deco

@deco(False)                        ##备注:当装饰器有参数时,装饰器语法糖后面需要加括号,并根据需要填写对应的参数,否则会没有任何执行结果。
                                    ##如果装饰器没有参数,后面的括号可以省略
                                    
def func(insert):
    print('hello ',insert,' world')


if __name__=="__main__":
    func('my')

 

装饰器调用顺序

装饰器是可以叠加使用的,那么这是就涉及到装饰器调用顺序了。对于Python中的"@"语法糖,装饰器的调用顺序与使用 @ 语法糖声明的顺序相反。 (靠近函数近的装饰器先调用)

在上面这个例子中,"addFunc(3, 8) = deco_1(deco_2(addFunc(3, 8)))"

def dec(func):
    def f(num):
        print('dec0-0')
        func(num)
        print('dec0-1')
    return f

def dec1(func):
    def f(num):
        print('dec1-0')
        func(num)
        print('dec1-1')
    return f


@dec1
@dec
def myfunc(num):
    print('hello')
    print(num)
    time.sleep(0.6)
    print('world')


if __name__=="__main__":
    myfunc(5)

上例的输出结果为:

dec1-0
dec0-0
hello
5
world
dec0-1
dec1-1

由输出字符串的嵌套顺序可以看出装饰器的调用顺序

Python内置装饰器

在Python中有三个内置的装饰器,都是跟class相关的:staticmethod、classmethod 和property。

  • staticmethod 是类静态方法,其跟成员方法的区别是没有 self 参数,并且可以在类不进行实例化的情况下调用
  • classmethod 与成员方法的区别在于所接收的第一个参数不是 self (类实例的指针),而是cls(当前类的具体类型)
  • property 是属性的意思,表示可以通过通过类实例直接访问的信息

对于staticmethod和classmethod这里就不介绍了,通过一个例子看看property。

注意,对于Python新式类(new-style class),如果将上面的 "@var.setter" 装饰器所装饰的成员函数去掉,则Foo.var 属性为只读属性,使用 "foo.var = 'var 2'" 进行赋值时会抛出异常。但是,对于Python classic class,所声明的属性不是 read-only的,所以即使去掉"@var.setter"装饰器也不会报错。

总结

本文介绍了Python装饰器的一些使用,装饰器的代码还是比较容易理解的。只要通过一些例子进行实际操作一下,就很容易理解了。

作者:田小计划

出处:http://www.cnblogs.com/wilber2013/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

如果觉得不错,请点击推荐关注

 

 

 

 

 

 

code snippet widget 详细X

  没有英汉互译结果
  请尝试网页搜索

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值