Python10000小时计划——装饰器
●在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
●装饰器的意义,大概就是把原来的函数包装起来在传给原函数,这样原函数不用改动就能增加功能。
●@是装饰器的语法糖,在定义的时候使用,防止再一次的赋值
●装饰模式有很多经典的使用场景,例如插入日志、性能测试、事务处理等等,有了装饰器,就可以提取大量函数中与本身功能无关的类似代码,从而达到代码重用的目的。
●通过装饰器log在调用函数前输出调用函数的名称
二层嵌套
#定义一个能打印日志的decoratoe def log(func): #接受一个函数作为参数 #@functools.wraps(func) def wrapper(*args,**kw): #前面是list后面是字典 print('call %s():' % func.__name__) return func(*args,**kw) return wrapper #返回一个函数 #观察上面的log,因为它是一个decorator,所以接受一个函数作为参数1,并返回一个函数。 #我们要借助Python的@语法,把decorator置于函数的定义处: @log def now(): print('2018/6/6') #调用函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志
now()
测试结果

三层嵌套
#如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本 def log(text): def decorator(func): #@functools.wraps(func) def wrapper(*args,**kw): print('%s %s():' % (text,func.__name__)) return func(*args,**kw) return wrapper return decorator #这个三层嵌套的decorator用法如下: @log('excute') #文本text=‘excute’ def rightnow(): print('2018/6/6') #调用函数 rifhtnow()
和两层嵌套的decorator相比,3层嵌套的效果是这样的:
>>> now = log('execute')(now)
测试结果

以上两种decoratror的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,
它有__name__等属性,但你去看经过decorator装饰之后的函数,他们的__name__已经从原来的'now'变成了'wrapper'
print(now.__name__)

因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。
不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的(上述代码中#@functools.wraps(func))
import functools是导入functools模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()的前面加上@functools.wraps(func)即可。
练习题1
请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间:
# -*- coding: utf-8 -*- import time, functools def metric(fn): @functools.wraps(fn) def wrapper(*args,**kw): start = time.time() res = fn(*args, **kw) end = time.time() print('%s executed in %s ms' % (fn.__name__,(end-start)*1000)) return res return wrapper # 测试 @metric def fast(x, y): time.sleep(0.0012) return x + y; @metric def slow(x, y, z): time.sleep(0.1234) return x * y * z; f = fast(11, 22) s = slow(11, 22, 33) if f != 33: print('测试失败!') elif s != 7986: print('测试失败!')

练习题2
请编写一个decorator,能在函数调用的前后打印出'begin call'和'end call'的日志。
import functools def log(pr): @functools.wraps(pr) def wrapper(*args,**kw): print('begin call') c=pr(*args,**kw) #与return func(*args,**kw)不同的是,这里利用赋值来调用函数 print('end call') return c #那么这里就只会返回函数的值,而不会再次调用函数了 return wrapper @log def now(): print ('1') now() #调用函数,别漏了

import functools def log(func): def wrapper(): print ('begin call') func() print ('end call') return wrapper @log def myage(): print('24') myage()

●多个装饰器的执行顺序
@a
@b
@c
def func():
print "aaa"
●等同于
a(b(c(fun)))

浙公网安备 33010602011771号