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()
以上程序运行结果如下:
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()
以上代码运行结果如下:
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()
运行结果如下:
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)
运行结果如下:
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)
运行结果如下:
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():
以上代码等效于 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)
运行结果:
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)
运行结果:
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)
运行结果:
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装饰器的使用的主要内容,如果未能解决你的问题,请参考以下文章