装饰器
首先,给出装饰器的框架:
def log(func): def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*args, **kw) return wrapper @log def now(): print('2018-6-14')
Python装饰器,本质上就是一个高阶函数。作用是给其它函数增加新的功能。借用python的@语法,可以将一个高阶函数定义为装饰器。
@符号的作用
但是,在学习廖雪峰老师的Python教程时,似懂非懂,尤其是看到@这个符号的时候,感觉一头雾水。现在回想来看,只要百度一下就能知道@符号在Python中的作用。它的作用就是修饰一个函数。位置在被修饰的函数的前一行,@之后是修饰函数的函数。 例如上述的 @log 起到的作用就是相当于执行了语句: now = log(now) ,现在或许难以理解这行代码的作用。接下来,进行一个对比,就能知道装饰器的威力了。
使用装饰器之前
#给下列函数增加新功能:调用函数时,打印函数名。 #要求:(1)不改变函数的定义,不改变函数的调用方式。 def now(): print('2015-3-25')
#增加一个高级函数 def call_name(func): def wrapper(*args, **kw): print('call %s()' % func.__name__) return func(*args, **kw) return wrapper
#调用now函数 now = call_name(now) #调用方式还是改变了(增加了一行) now()
使用装饰器之后
#给下列函数增加新功能:调用函数时,打印函数名。 #要求:(1)不改变函数的定义,不改变函数的调用方式。 #写一个装饰器 def call_name(func): def wrapper(*args, **kw): print('call %s()' % func.__name__) return func(*args, **kw) return wrapper @call_name #相当于执行语句 call_name(now) def now(): print('2015-3-25') #调用now函数 now() #这才是真正没改变调用方式
通过对比,我对装饰器的作用了解更加深刻了。下面是带参数的装饰器:
带参数的装饰器
上例,如果装饰器call_name(func)本身还要带参数,那么需要更加复杂的高阶函数。
1 def call_name(text): #text为装饰器的参数 2 def decorator(func): 3 def wrapper(*args, **kw): 4 print('%s call %s()' % (text, func.__name__)) 5 return func(*args, **kw) 6 return wrapper 7 return decorator 8 9 10 @call_name('execute ') #相当于 now = log('execute')(now) 11 def now(): 12 print('2015-3-25') 13 14 15 # 调用now函数 16 now()
装饰器练习
设计一个decorator(装饰器),它可作用于任何函数上,并打印该函数的执行时间:
1 import time 2 3 import functools 4 5 6 def metric(fn): 7 @functools.wraps(fn) 8 def wrapper(*args, **kw): 9 print('%s executed in %s' % 10 (fn.__name__, time.asctime(time.localtime(time.time())))) 11 return fn(*args, **kw) 12 return wrapper 13 14 # 测试 15 16 17 @metric 18 def fast(x, y): 19 time.sleep(0.0012) 20 return x + y 21 22 23 @metric 24 def slow(x, y, z): 25 time.sleep(0.1234) 26 return x * y * z 27 28 29 f = fast(11, 22) 30 s = slow(11, 22, 33) 31 32 print(f) 33 print(s) 34 35 if f != 33: 36 print('测试失败!') 37 elif s != 7986: 38 print('测试失败!') 39 else: 40 print('测试成功!')
编写一个decorator,能在函数调用的前后打印出'begin call'和'end call'的日志:
1 def call_name(func): 2 def wrapper(*args, **kw): 3 print('begin call:') 4 print('call %s()' % func.__name__) 5 call = func(*args, **kw) #函数在此时调用 6 print('end call.') 7 return call 8 return wrapper 9 10 11 @call_name 12 def now(): 13 print('2015-3-25') 14 15 16 # 调用now函数 17 now()
加大难度,写出一个@call_name
的decorator,使它既支持:
@log def f(): pass
又支持:
@log('execute') def f(): pass
源码实现:
1 import functools 2 3 4 def call_name(text): 5 if isinstance(text, str): #通过参数能够判断两种模式 6 def decorator(func): 7 @functools.wraps(func) 8 def wrapper(*args, **kw): 9 print('%s call %s()' % (text, func.__name__)) 10 return func(*args, **kw) 11 return wrapper 12 return decorator 13 else: 14 @functools.wraps(text) 15 def wrapper(*args, **kw): 16 print('call %s()' % text.__name__) 17 return text(*args, **kw) 18 return wrapper 19 20 21 @call_name 22 def now(): 23 print('2015-3-25') 24 25 26 # 调用now函数 27 now()