python装饰器的使用

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python装饰器的使用相关的知识,希望对你有一定的参考价值。

装饰器(装饰模式)有很多的使用场景,例如插入日志、性能测试、处理事务、缓存、权限校验等。有了装饰器,就可以提取大量函数中与本身功能无关的类似代码,从而达到代码重用的目的。

装饰器的作用就是为已经存在的对象添加额外的功能。

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

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

技术分享
 1 import time
 2 
 3 def deco(func):
 4     startTime = time.time()
 5     func()
 6     endTime = time.time()
 7     msecs = (endTime - startTime) * 1000
 8     print("--> elapsed time: %f ms" % msecs)
 9 
10 def myfunc():
11     print("Start myfunc")
12     time.sleep(0.6)
13     print("end myfunc")
14 
15 deco(myfunc)
16 myfunc()
View Code

以上程序运行结果如下:

Start myfunc
end myfunc
--> elapsed time: 599.999905 ms
Start myfunc
end myfunc

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

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

技术分享
 1 import time
 2 
 3 def deco(func):
 4     def wrapper():
 5         startTime = time.time()
 6         func()
 7         endTime = time.time()
 8         msecs = (endTime - startTime) * 1000
 9         print("--> elapsed time: %f ms" % msecs)
10     return wrapper
11 
12 def myfunc():
13     print("Start myfunc")
14     time.sleep(0.6)
15     print("end myfunc")
16 
17 print("myfunc is : ",  myfunc.__name__)
18 myfunc = deco(myfunc)
19 print("myfunc is :",  myfunc.__name__)
20 print("                 ")
21 
22 myfunc()
View Code

以上代码运行结果如下:

myfunc is :  myfunc
myfunc is : wrapper
                 
Start myfunc
end myfunc
--> elapsed time: 599.999905 ms

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

 

装饰器

在python中,可以使用 @ 来精简装饰器的代码:

技术分享
 1 import time
 2 def deco(func):
 3     def wrapper():
 4         startTime = time.time()
 5         func()
 6         endTime = time.time()
 7         msecs = (endTime - startTime) * 1000
 8         print("--> elapsed time: %f ms" %msecs)
 9     return wrapper
10 
11 @deco
12 def myfunc():
13     print("start myfunc")
14     time.sleep(0.6)
15     print("end myfunc")
16 
17 print("myfunc is:", myfunc.__name__)
18 print("                 ")
19 myfunc()
View Code

运行结果如下:

myfunc is: wrapper
                 
start myfunc
end myfunc
--> elapsed time: 615.000010 ms

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

被装饰的函数带参数

前面的例子中,被装饰的函数是没有带参数的,下面看被装饰的函数带参数的例子:

技术分享
 1 import time
 2 
 3 def deco(func):
 4     def wrapper(a, b):
 5         startTime = time.time()
 6         func(a, b)
 7         endTime = time.time()
 8         msecs = (endTime - startTime) * 1000
 9         print("--> elapsed time %f ms" % msecs)
10     return wrapper
11 
12 @deco
13 def addFunc(a, b):
14     print("start addfunc")
15     time.sleep(0.6)
16     print("result is %d" %(a + b))
17     print("end addfunc")
18 
19 addFunc(3, 8)
View Code

运行结果如下:

start addfunc
result is 11
end addfunc
--> elapsed time 599.999905 ms

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

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

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

带参数的装饰器

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

技术分享
 1 import time
 2 
 3 def deco(arg = True):
 4     if arg:
 5         def _deco(func):
 6             def wrapper(*args, **kwargs):
 7                 startTime = time.time()
 8                 func(*args, **kwargs)
 9                 endTime = time.time()
10                 msecs = (endTime - startTime) * 1000
11                 print("-->elapsed time: %f ms" %msecs)
12             return wrapper
13     else:
14         def _deco(func):
15             return func
16     return _deco
17 
18 @deco(False)
19 def myFunc():
20     print("start myFunc")
21     time.sleep(0.6)
22     print("end myFunc")
23 
24 @deco(True)
25 def addFunc(a, b):
26     print("start addFunc")
27     time.sleep(0.6)
28     print("result %d" %(a + b))
29     print("end addFunc")
30 
31 print("myfunc is:", myFunc.__name__)
32 myFunc()
33 print("                     ")
34 print("addFunc is:", addFunc.__name__)
35 addFunc(3, 8)
View Code

运行结果如下:

myfunc is: myFunc
start myFunc
end myFunc
                     
addFunc is: wrapper
start addFunc
result 11
end addFunc
-->elapsed time: 603.999853 ms

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

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

装饰器调用顺序

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

装饰器的顺序:

技术分享
1 @a
2 @b
3 @c
4 def f():
View Code

以上代码等效于 f a(b(c(f)))

技术分享
 1 def deco_1(func):
 2     print("enter into deco_1")
 3     def wrapper(a, b):
 4         print("enter into deco_1_wrapper")
 5         func(a, b)
 6     return wrapper
 7 
 8 def deco_2(func):
 9     print("enter into deco_2")
10     def wrapper(a, b):
11         print("enter into deco_2_wrapper")
12         func(a, b)
13     return wrapper
14 
15 @deco_1
16 @deco_2
17 def addFunc(a, b):
18     print("result is %d" % (a + b))
19 
20 addFunc(3, 8)
View Code

运行结果:

enter into deco_2
enter into deco_1
enter into deco_1_wrapper
enter into deco_2_wrapper
result is 11

在这个例子中,”addFunc(3, 8) = deco_1(deco_2(addFunc(3, 8)))”。

Python内置装饰器

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

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

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

技术分享
 1 class Foo(object):
 2     def __init__(self, var):
 3         super(Foo, self).__init__()
 4         self._var = var
 5 
 6     @property
 7     def var(self):
 8         return self._var
 9 
10     @var.setter
11     def var(self, var):
12         self._var = var
13 
14 foo = Foo(var 1)
15 print(foo.var)
16 foo.var = var 2
17 print(foo.var)
View Code

运行结果:

var 1
var 2

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

补充内容:不同函数参数在装饰器上的应用

 

技术分享
 1 def outer(func):
 2     def inner(*args, **kwargs):
 3         print("123")
 4         ret = func(*args, **kwargs)#调用原函数,可以接收任意参数
 5         print("456")
 6         return ret
 7     return inner#返回的是一个函数,执行函数内存,希望index也返回到函数内存。
 8 
 9 @outer
10 def f1(arg):
11     print("f1")
12     return f1
13 
14 f1(123)
15 
16 @outer
17 def f2(a1, a2, a3):
18     print("F2")
19     return f2
20 
21 f2(1, 2, 3)#原函数需要几个参数,就传几个参数
22 
23 @outer
24 def index(a1, a2):
25     print("非常复杂")
26     return a1 + a2
27 #只要函数应用装饰器,函数就会被重新定义,定义为装饰器的内层函数,
28 r = index(1, 2)
29 print(r)
View Code

 

运行结果:

123
f1
456
123
F2
456
123
非常复杂
456
3

特别说明:每一次函数调用,都是以打印出123开始,打印出456结束。

 

总结

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

转载:http://www.cnblogs.com/wilber2013/

部分内容参考:https://www.zhihu.com/question/26930016

写在文章之后,这是博客园的第一次转载,从伯乐在线上半copy下来的,后来才发现 原作者也是博客园的,囧……

本文内容运行环境:pycharm专业版2017.2.1,python3.6.1的解释器








































以上是关于python装饰器的使用的主要内容,如果未能解决你的问题,请参考以下文章

python 装饰器的使用

python 函数 装饰器的使用方法

python使用上下文对代码片段进行计时,非装饰器

Python装饰器的实现和万能装饰器

转发对python装饰器的理解

python 多个装饰器的调用顺序