刚看到Python装饰器时, 觉得很神奇。简单实验下,发现也就那么回事。但是慢慢的看到越来越多的装饰器。很多时候又不了解到底是怎么回事了。
最后还是决定好好研究下。
先看看一些实例, 然后再来分析下原理
假设我们有如下的基本函数
def do_something():
for i in range(1000000):
pass
print "play game"
do_something()
结果如下: play game
需求1: 统计函数的执行时间
1. 不是装饰器的装饰器
import time
def decorator(fun):
start = time.time()
fun()
runtime = time.time()-start
print runtime
def do_something():
for i in range(1000000):
pass
print "play game"
decorator(do_something)
结果如下:
play game
0.0299999713898
这种实现看上去还可以,但是每次调用的是decorator,还要把函数作为一个参数传入。这样需要修改调用的地方,使用起来就不方便了。
2. 最简单的装饰器
import time
def decorator(fun):
def wrapper():
start = time.time()
fun()
runtime = time.time()-start
print runtime
return wrapper
@decorator
def do_something(): # 等价于 do_something = decorator(do_something)
for i in range(1000000):
pass
print "play game"
do_something()
结果如下:
play game
0.0329999923706
装饰器是在函数定义时前面加@,然后跟装饰器的实现函数。可以看出,现在只要直接调用do_something就可以了。调用的地方不要作任何修改。
3. 目标函数带固定参数的装饰器
import time
def decorator(fun):
def wrapper(name):
start = time.time()
fun(name)
runtime = time.time()-start
print runtime
return wrapper
@decorator
def do_something(name):
for i in range(1000000):
pass
print "play game " + name
do_something("san guo sha")
结果如下:
play game san guo sha
0.039999961853
实现很简单, 就是给wrapper函数参加相同的参数
4. 目标函数带不固定参数的装饰器
import time
def decorator(fun):
def wrapper(*args, **kwargs):
start = time.time()
fun(*args, **kwargs)
runtime = time.time()-start
print runtime
return wrapper
@decorator
def do_something(name):
for i in range(1000000):
pass
print "play game " + name
@decorator
def do_something2(user, name):
for i in range(1000000):
pass
print user+" play game " + name
do_something("san guo sha")
do_something2("wang xiao er","san guo sha")
结果如下:
play game san guo sha
0.029000043869
wang xiao er play game san guo sha
0.0310001373291
需求2: 目标函数每次调用重复执行指定的次数
5. 让装饰器带参数
import time
def decorator(max):
def _decorator(fun):
def wrapper(*args, **kwargs):
start = time.time()
for i in xrange(max):
fun(*args, **kwargs)
runtime = time.time()-start
print runtime
return wrapper
return _decorator
@decorator(2)
def do_something(name):
for i in range(1000000):
pass
print "play game " + name
do_something("san guo sha")
结果如下:
play game san guo sha
play game san guo sha
0.0600001811981
6. 原理
看了这么多实例, 装饰器的基本类型也基本上都有了。是不是清楚了呢?
如果还是不清楚,那就继续看下面的内容。
1 不带参数的装饰器
@a_decorator
def f(...):
...
#经过a_decorator后, 函数f就相当于以f为参数调用a_decorator返回结果。
f = a_decorator(f)
来分析这个式子, 可以看出至少要满足以下几个条件
- 装饰器函数运行在函数定义的时候
- 装饰器需要返回一个可执行的对象
- 装饰器返回的可执行对象要兼容函数f的参数
2 验证分析
1 装饰器运行时间
import time
def decorator(fun):
print "decorator"
def wrapper():
print "wrapper"
start = time.time()
fun()
runtime = time.time()-start
print runtime
return wrapper
@decorator
def do_something():
for i in range(1000000):
pass
print "play game"
结果如下:
decorator
可以看出, 这里的do_something并没有调用, 但是却打印了decorator, 可wrapper没有打印出来。也就是说wrapper是在do_something调用的时候执行的。
2 返回可执行的对象
import time
def decorator(fun):
print "decorator"
def wrapper():
print "wrapper"
start = time.time()
fun()
runtime = time.time()-start
print runtime
return None
@decorator
def do_something():
for i in range(1000000):
pass
print "play game"
do_something()
结果如下:
decoratorTraceback (most recent call last):
File "deco.py", line 17, in <module>
do_something()
TypeError: 'NoneType' object is not callable
3 兼容函数f的参数
import time
def decorator(fun):
print "decorator"
def wrapper(): # 有传入参数 这里也要有哦!
print "wrapper"
start = time.time()
fun()
runtime = time.time()-start
print runtime
return wrapper
@decorator
def do_something(name):
for i in range(1000000):
pass
print "play game"
do_something("san guo sha")
结果如下:
decoratorTraceback (most recent call last):
File "deco.py", line 17, in <module>
do_something("san guo sha")
TypeError: wrapper() takes no arguments (1 given)
看到这里, 至少对不带参数的装饰器应该全弄清楚了, 也就是说能到看山还是山了。
# def fun1():
# print('功能1')
#
# def foo():
# print('foo函数正在运行')
import time
def writeLog(func):
print('访问了方法名:', func.__name__, ' 时间:', time.asctime())
def funOut(func):
def funIn(x, y): # 此处的参数 跟 add 参数一样
writeLog(func)
return func(x, y)
return funIn
@funOut
def sum(a, b):
return a + b
result = sum(10, 20)
print('两数的和:', result)
# 功能函数三个参数
def funOut2(func):
def funcIn(a, b, c): # 此处的参数 跟 add 参数一样
writeLog(func)
return func(a, b, c)
return funcIn
@funOut2
def add(a, b, c):
return a + b + c
result = add(10, 20, 30)
print('三个数的和:', result)
3 带参数的装饰器
这里就给一个式子, 剩下的问题可以自己去想
@decomaker(argA, argB, ...)
def func(arg1, arg2, ...):
pass
#这个式子相当于
func = decomaker(argA, argB, ...)(func)
4 被装饰过的函数的函数名
import time
def decorator(fun):
def wrapper():
start = time.time()
fun()
runtime = time.time()-start
print runtime
return wrapper
@decorator
def do_something():
print "play game"
print do_something.__name__
结果如下:
wrapper
可以看出, do_something的函数名变成了wrapper,这不是我们想要的。原因估计各位也都清楚了。那要怎么去解决呢?
import time
def decorator(fun):
def wrapper():
start = time.time()
fun()
runtime = time.time()-start
print runtime
wrapper.__name__ = fun.__name__
return wrapper
@decorator
def do_something():
print "play game"
print do_something.__name__
结果如下:do_something
但是这个看起来是不是很不专业, python的unctools.wraps提供了解决方法
import time
import functools
def decorator(fun):
@functools.wraps(fun)
def wrapper():
start = time.time()
fun()
runtime = time.time()-start
print runtime
return wrapper
@decorator
def do_something():
print "play game"
print do_something.__name__
结果如下:do_something
到此为止, 你是不是觉得已经完全明白了呢?
但事实是, 这其实还不够
7. 装饰器类
需求3: 让函数只能运行指定的次数
前面我们讲的都是函数式的装饰器, 那么类能不能成为装饰器呢?
import time
import functools
class decorator(object):
def __init__(self, max):
self.max = max
self.count = 0
def __call__(self, fun):
self.fun = fun
return self.call_fun
def call_fun(self, *args, **kwargs):
self.count += 1
if ( self.count == self.max):
print "%s run more than %d times"%(self.fun.__name__, self.max)
elif (self.count<self.max):
self.fun(*args, **kwargs)
else:
pass
@decorator(10)
def do_something():
print "play game"
@decorator(15)
def do_something1():
print "play game 1"
for i in xrange(20):
do_something()
do_something1()
结果如下:
play game
play game 1
play game
play game 1
play game
play game 1
play game
play game 1
play game
play game 1
play game
play game 1
play game
play game 1
play game
play game 1
play game
play game 1
do_something run more than 10 times
play game 1
play game 1
play game 1
play game 1
play game 1
do_something1 run more than 15 times
是不是感觉有点怪, 但它确实是可行的。
在Python中, 其实函数也是对象。 反过来, 对象其实也可以像函数一样调用, 只要在类的方法中实现__call__方法。回想一下创建对象的过程
class A:
def __init__(self):
pass
a = A()
这其实和函数调用没什么区别, 那么把这个式子代入到之前两个装饰器的式子中,结果如下:
带参数的装饰器
fun = A.__init__(args)(fun)
不带参数的装饰器
fun = A.__init__(fun)()
现在装饰器的内容基本差不多了。 还有一些问题, 可以自己去尝试研究。
PS:多个装饰器一起使用(函数嵌套)
#给foo函数,新增功能
#在调用foo函数前,输出 ’I am foo‘
def funOut(func):
print('装饰器1')
def funIn():
print('I am foo')
func()
return funIn
def funOut2(func):
print('装饰器2')
def funIn():
print('hello')
func()
return funIn
@funOut2
@funOut
def foo():
print('foo函数正在运行')
#使用闭包调用
# foo=funOut(foo)
foo()
def funOut1(func):
print("装饰器1")
def funIn():
print("i am foo")
func()
return funIn
def funOut2(func):
print("装饰器2")
def funIn():
print('hello')
func()
return funIn
PS:通用装饰器
import time
def funcOut(func):
def funcIn(*args, **kwargs):
writeLog(func)
return func(*args, **kwargs)
return funcIn
def writeLog(func):
print('访问方法名:', func.__name__, ' 时间:', time.asctime())
@funcOut
def sum(a, b):
return a + b
@funcOut
def add(a, b, c):
return a + b + c
result = sum(10, 20)
print('两个数的和:', result)
result = add(10, 20, 30)
print('三个数的和:', result)
8 参考资料
https://www.python.org/dev/peps/pep-0318/
https://docs.python.org/2/library/functools.html