装饰器,简而言之就是在不改变原函数任何代码(包括调用方式)的情况下为函数增加额外的功能。特殊之处在于装饰器的返回值也是一个函数。
一、装饰器介绍
基础栗子
给装饰器传参数
栗子
基于类实现的装饰器
传参数
关于wrapt
下面先举个简单的栗子:
1 import time 2 3 4 def count_time(foo_func): 5 def inner(): 6 print("inner func start!") 7 start_time = time.time() 8 time.sleep(1) # 1s后执行 9 foo_func() 10 end_time = time.time() 11 print("the func cost:{:.2f}".format(end_time-start_time)) 12 return inner 13 14 15 @count_time 16 def foo(): 17 # 利用装饰器计算foo函数执行时间 18 print("foo execute done!!") 19 20 21 if __name__ == '__main__': 22 foo() # 调用foo函数
既然说增加了装饰器,那么原函数的代码和调用方式都没改变。
其中 @count_time 就等同于 foo = count_time(foo)
如果要给装饰器传参数。。如下:
import time def outer(name): def count_time(foo_func): def inner(): print("inner func start!") print("装饰器传进来的参数是:{}".format(name)) start_time = time.time() time.sleep(1) # 1s后执行 foo_func() end_time = time.time() print("the func cost:{:.2f}".format(end_time-start_time)) return inner return count_time @outer(name="jack") def foo(): # 利用装饰器计算foo函数执行时间 print("foo execute done!!") if __name__ == '__main__': foo() # 调用foo函数
若要给装饰器传参数,只需要再给函数增加一层函数就行了。
但是由于使用装饰器后会改变函数的一些内置变量比如__name__,__doc__。看栗子:
import time from functools import wraps def count_time(foo_func): # @wraps(foo_func) def inner(): """ now in inner func """ print("inner func start!") start_time = time.time() time.sleep(1) # 1s后执行 foo_func() end_time = time.time() print("foo func cost:{:.2f}".format(end_time-start_time)) return inner @count_time def foo(): """ now in foo func """ # 利用装饰器计算foo函数执行时间 print("foo func doc is {}".format(foo.__doc__)) # 输出 now in inner func print("foo func name is {}".format(foo.__name__)) # 输出 inner print("foo execute done!!") if __name__ == '__main__': foo() # 调用foo函数
所以需要纠正回来,该如何呢?来
只需要引入functools模块下的wraps函数
在inner函数上面使用@wraps(func),即再次装饰下inner函数即可
然后输出语句会变为:
print("foo func doc is {}".format(foo.__doc__)) # 输出 now in foo func
print("foo func name is {}".format(foo.__name__)) # 输出 foo
要给被装饰的函数传参数:
栗子如下:
给函数传参数
import time from functools import wraps def count_time(foo_func): @wraps(func) def inner(*args, **kwargs): """ now in inner func """ print("inner func start!") start_time = time.time() time.sleep(1) # 1s后执行 foo_func(*args, **kwargs) end_time = time.time() print("foo func cost:{:.2f}".format(end_time-start_time)) return inner @count_time def foo(name, age, *args, **kwargs): """ now in foo func """ # 利用装饰器计算foo函数执行时间 print(name,age) print(args,kwargs) print("foo execute done!!") if __name__ == '__main__': foo("jack", 21, "student", department="信工学院") # 调用foo函数
增加的地方有调用时传了四个参数,分别是位置参数和关键字参数(最后一个是关键字参数,关键字参数必须在位置参数之后传入)
在装饰器内部的inner函数的参数那里增加了*args, **kwargs,和inner内部的func(*args, **kwargs)这里
args表示所有的位置参数都在args这个元组里面
kwargs表示所有的关键字参数都在args这个字典里面
二、基于类实现的装饰器
看栗子:
class Logging(object): def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): print("[DEBUG]: enter function {func}()".format( func=self.func.__name__)) return self.func(*args, **kwargs) @Logging def say(something): print( "say {}!".format(something)) say("hello")
输出:
[DEBUG]: enter function say()
say hello!
解答:如果用类来实现装饰器的话,必须实现类中的内置方法__init__和__call__这两个方法
其中__init__用来接收被装饰函数的名字,__call__用来对函数进行装饰以及增加额外的功能
若要给装饰器传参数。还是看栗子:
class logging(object): def __init__(self, level='INFO'): self.level = level def __call__(self, func): # 接受函数 def wrapper(*args, **kwargs): print("[{level}]: enter function {func}()".format( level=self.level, func=func.__name__)) func(*args, **kwargs) return wrapper # 返回函数 @logging(level='INFO') def say(something): print("say {}!".format(something)) say("hello")
这里的调用就是给装饰器传参数。
__init__方法参数接收的不再是被装饰函数的函数名,而是装饰器传来的参数
__call__方法参数接收被装饰函数的函数名,里面再嵌套一层函数用来接收装饰函数传的参数
最后,拓展一下wrapt这个装饰器:
import wrapt @wrapt.decorator def logging(wrapped, instance, args, kwargs): # instance is must need print("[DEBUG]: enter {}()".format(wrapped.__name__)) return wrapped(*args, **kwargs) @logging def say(something): pass say("hello")
这样看起来装饰器就更加明了了,,不用在装饰器内部再嵌套函数了。
上面的写法必须要按照上面的来
其中wrapped就表示被装饰的函数,
若要给装饰器传参数,也是再嵌套一层就可以了。