我们将通过举例一个小函数,通过使用装饰器增加功能(实现方法不一定最好,这里仅仅是为了说明装饰器用法,不要钻牛角尖)。
1.最简单的函数
这是一个最简单的函数,打印这个计算的结果和开始结束时间(此处计算量很小,几乎同时完成,所以开始结束时间一样没有关系)
源码:
# coding: utf-8 import time def do(): print 'start:', time.time() result = 1 + 1 print 'result:', result print 'end:', time.time() do()
输出:
start: 1508143969.67 result: 2 end: 1508143969.67
2.使用装饰器增加功能,打印函数执行开始和结束时间
在python中,函数是可以作为参数传递给另一个函数的。装饰器其实就是以一个函数为参数的普通函数,然后又返回一个函数。 请注意此例的注释和执行结果
- 包装器接受一个函数作为参数,又返回一个新的函数
- 包装的过程是在程序启动时就执行的,直接返回了新函数替换了def处的函数,并非等到执行do()才替换该函数
源码:
# coding: utf-8 import time def print_run_time(func): # 接受一个函数作为参数 def wrapper(): # 构造的新的函数,用于返回替换函数func print 'start:', time.time() func() print 'end:', time.time() return wrapper # 注意没有后缀(),因为要返回构造的新函数,而不是新函数调用结果 def do(): result = 1 + 1 print 'result:', result print u'调用原始的do函数:' do() print u'根据装饰器的定义手动调用,用装饰器包装do函数:' print_run_time(do)() print u'根据装饰器的定义手动调用,用装饰器包装do函数(分步骤调用,思考整个调用过程和原理):' new_do = print_run_time(do) # 把do函数作为参数,注意do没有后缀() print u'装饰器返回一个函数', type(new_do) new_do() # 调用装饰后的新函数
输出:
调用原始的do函数: result: 2 根据装饰器的定义手动调用,用装饰器包装do函数: start: 1508145089.23 result: 2 end: 1508145089.23 根据装饰器的定义手动调用,用装饰器包装do函数(分步骤调用,思考整个调用过程和原理): 装饰器返回一个函数 <type 'function'> start: 1508145089.23 result: 2 end: 1508145089.23
3.使用语法糖包装函数
@语法糖相当于new_do = print_run_time(do)
源码:
# coding: utf-8 import time def print_run_time(func): def wrapper(): print 'start:', time.time() func() print 'end:', time.time() return wrapper @print_run_time def do(): result = 1 + 1 print 'result:', result do()
输出:
start: 1508145584.21 result: 2 end: 1508145584.21
4.包装带参数的函数
do函数改为需要输入x,y然后计算结果
源码:
# coding: utf-8 import time def print_run_time(func): def wrapper(x, y, *args, **kwargs): # wrapper为构造的新函数,那么原来func的参数都被传递给了wrapper print 'start:', time.time() func(x, y, *args, **kwargs) print 'end:', time.time() return wrapper @print_run_time def do(x, y): result = x + y print 'result:', result do(1, 2)
输出:
start: 1508145970.8 result: 3 end: 1508145970.8
5.带参数的装饰器
之前输出的时间是时间戳,现在我们要根据装饰器的参数来决定打印时间戳或者日期时间。 这就需要一个工厂函数(根据参数创建一个装饰器,然后再去包装原始的do函数),原理同以上将的装饰器,不再赘述,仔细对比代码变化即可
源码:
# coding: utf-8 import time, datetime def print_run_time(time_type): # 多了这一层来根据参数创建一个装饰器 def wrapper_factory(func): # 根据参数构造的装饰器 def wrapper(x, y, *args, **kwargs): if time_type == 'datetime': print 'start:', datetime.datetime.now() func(x, y, *args, **kwargs) print 'end:', datetime.datetime.now() else: print 'start:', time.time() func(x, y, *args, **kwargs) print 'end:', time.time() return wrapper return wrapper_factory @print_run_time('datetime') def do(x, y): result = x + y print 'result:', result do(1, 2)
输出:
start: 2017-10-16 17:37:25.286000 result: 3 end: 2017-10-16 17:37:25.286000
6.带参数的装饰器(使用类的__call__
)
以上带参数装饰器使用工厂函数,多了一层更加使人迷惑,其实原理不变,这里把装饰器工厂函数用类来实现更加清晰明了
源码:
# coding: utf-8 import time, datetime class print_run_time: def __init__(self, time_type): # 装饰器参数 self.time_type = time_type # __call__使实例变为可调用对象,相当于一个函数 def __call__(self, func): def wrapper(x, y, *args, **kwargs): if self.time_type == 'datetime': print 'start:', datetime.datetime.now() func(x, y, *args, **kwargs) print 'end:', datetime.datetime.now() else: print 'start:', time.time() func(x, y, *args, **kwargs) print 'end:', time.time() return wrapper @print_run_time('datetime') def do(x, y): result = x + y print 'result:', result do(1, 2)
输出:
start: 2017-10-16 17:45:04.094000 result: 3 end: 2017-10-16 17:45:04.094000