【转】python装饰器详解

 

 

转自:https://blog.csdn.net/xiangxianghehe/article/details/77170585

           http://python.jobbole.com/82344/

 

“你会Python嘛?” 
“我会!” 
‘那你给我讲下Python装饰器吧!’ 
“Python装饰器啊?我没用过哎” 

简而言之,python装饰器就是用于拓展原来函数功能的一种函数,这个函数的特殊之处在于它的返回值也是一个函数,使用python装饰器的好处就是在不用更改原函数代码的前提下给函数增加新的功能。

一般而言,我们想要拓展原来的函数代码,最直接的方法就是侵入代码里面修改,例如:

import time
def func():
    print("hello")
    time.sleep(1)
    print("world")

这是我们最原始的一个函数,然后我们试图记录下这个函数执行的总时间,那最简单的做法就是:

#原始侵入,篡改原函数
import time
def func():
    startTime = time.time()

    print("hello")
    time.sleep(1)
    print("world")
    endTime = time.time()

    msecs = (endTime - startTime)*1000
    print("time is %d ms" %msecs)

但是如果你的Boss在公司里面和你说:“小祁,这段代码是我们公司的核心代码,你不能直接去改我们的核心代码。”那该怎么办呢,我们仿照装饰器先自己试着写一下:

import time

def deco(func):
    def wrapper():
        startTime = time.time()
        func()
        endTime = time.time()
        msecs = (endTime - startTime)*1000
        print("time is %d ms" %msecs)
    return wrapper



def func():
    print("hello")
    time.sleep(1)
    print("world")

if __name__ == '__main__':
    #f = func #这里f被赋值为func,执行f()就是执行func()
    print("func.__name__ is", func.__name__)
    func = deco(func)
    print("func.__name__ is", func.__name__)



输出:
func.__name__ is func
func.__name__ is wrapper

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

这里我们定义了一个函数deco,它的参数是一个函数,然后给这个函数嵌入了计时功能。然后你可以拍着胸脯对老板说,看吧,不用动你原来的代码,我照样拓展了它的函数功能。 
然后你的老板有对你说:“小祁,我们公司核心代码区域有一千万个func()函数,从func01()到func1kw(),按你的方案,想要拓展这一千万个函数功能,就是要执行一千万次deco()函数,这可不行呀,我心疼我的机器。” 
好了,你终于受够你老板了,准备辞职了,然后你无意间听到了装饰器这个神器,突然发现能满足你闫博士的要求了。 

装饰器语法糖

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

#既不需要侵入,也不需要函数重复执行
import time

def deco(func):
    def wrapper():
        startTime = time.time()
        func()
        endTime = time.time()
        msecs = (endTime - startTime)*1000
        print("time is %d ms" %msecs)
    return wrapper



@deco
def func():
    print("hello")
    time.sleep(1)
    print("world")

if __name__ == '__main__':
    #f = func #这里f被赋值为func,执行f()就是执行func()
    print("func.__name__ is", func.__name__)
    func()
    print("func.__name__ is", func.__name__)

输出:
func.__name__ is wrapper
hello
world
time is 1000 ms
func.__name__ is wrapper

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

这里的deco函数就是最原始的装饰器,它的参数是一个函数,然后返回值也是一个函数。其中作为参数的这个函数func()就在返回函数wrapper()的内部执行。然后在函数func()前面加上@deco,func()函数就相当于被注入了计时功能,现在只要调用func(),它就已经变身为“新的功能更多”的函数了。

被装饰的函数带参数

#带有参数的装饰器
import time

def deco(func):
    def wrapper(a,b):
        startTime = time.time()
        func(a,b)
        endTime = time.time()
        msecs = (endTime - startTime)*1000
        print("time is %d ms" %msecs)
    return wrapper


@deco
def func(a,b):
    print("hello,here is a func for add :")
    time.sleep(1)
    print("result is %d" %(a+b))

if __name__ == '__main__':
    f = func
    f(3,4)
    #func()

输出:
hello,here is a func for add :
result is 7
time is 1000 ms

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

也就是说这时,”func(3, 8) = deco(func(3, 8))”。

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

#装饰带有不定参数函数的装饰器
import time

def deco(func):
    def wrapper(*args, **kwargs):
        startTime = time.time()
        func(*args, **kwargs)
        endTime = time.time()
        msecs = (endTime - startTime)*1000
        print("time is %d ms" %msecs)
    return wrapper


@deco
def func(a,b):
    print("hello,here is a func for add :")
    time.sleep(1)
    print("result is %d" %(a+b))

@deco
def func2(a,b,c):
    print("hello,here is a func for add :")
    time.sleep(1)
    print("result is %d" %(a+b+c))


if __name__ == '__main__':
    f = func
    func2(3,4,5)
    f(3,4)
    #func()

输出:

  hello,here is a func for add :
  result is 12
  time is 1000 ms
  hello,here is a func for add :
  result is 7
  time is 1000 ms

带参数的装饰器

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

#带有参数的装饰器
import time

def deco(arg = True):
    if arg:
        def _deco(func):
            def wrapper(*args, **kwargs):
                startTime = time.time()
                func(*args, **kwargs)
                endTime = time.time()
                msecs = (endTime - startTime)*1000
                print("time is %d ms" %msecs)
            return wrapper
    else:
        def _deco(func):
            return func
    return _deco


@deco(False)
def func(a,b):
    print("hello,here is a func for add :")
    time.sleep(1)
    print("result is %d" %(a+b))

@deco(True)
def func2(a,b,c):
    print("hello,here is a func for add :")
    time.sleep(1)
    print("result is %d" %(a+b+c))


if __name__ == '__main__':
    print("func.__name__ is",func.__name__)
    func(3,4)
    print("func2.__name__ is",func2.__name__)
    func2(4,5,6)
    #func()


输出:
func.__name__ is func
hello,here is a func for add :
result is 7
func2.__name__ is wrapper
hello,here is a func for add :
result is 15
time is 1000 ms

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

这时候,”func(3, 4) = deco(False)(func(3, 8))”,”func2() = deco(True)(func2(4,5,6))”

装饰器调用顺序

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

#多个装饰器

import time

def deco01(func):
    def wrapper(*args, **kwargs):
        print("this is deco01")
        startTime = time.time()
        func(*args, **kwargs)
        endTime = time.time()
        msecs = (endTime - startTime)*1000
        print("time is %d ms" %msecs)
        print("deco01 end here")
    return wrapper

def deco02(func):
    def wrapper(*args, **kwargs):
        print("this is deco02")
        func(*args, **kwargs)

        print("deco02 end here")
    return wrapper

@deco01
@deco02
def func(a,b):
    print("hello,here is a func for add :")
    time.sleep(1)
    print("result is %d" %(a+b))



if __name__ == '__main__':
    f = func
    f(3,4)
    #func()

'''
this is deco01
this is deco02
hello,here is a func for add :
result is 7
deco02 end here
time is 1003 ms
deco01 end here
'''

在这个例子中,”func(3, 4) = deco01(deco02(func(3, 4)))”。

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装饰器的一些使用,装饰器的代码还是比较容易理解的。只要通过一些例子进行实际操作一下,就很容易理解了。

posted on 2018-07-07 20:01  欢喜等大年  阅读(101)  评论(0编辑  收藏  举报

导航