python中的装饰器主要用于在已有函数实现功能前附加需要输出的信息,下面将用实例展示我如何写装饰器。
首先分别尝试写装饰器装饰一个无参函数和一个有参函数
1 def my_log(func): 2 def wrapper(): 3 print('decorator works') 4 func() 5 return wrapper #返回的只是wrapper函数对象,此时并没有运行 6 7 @my_log 8 def run(): # run=my_log(run)=wrapper 返回的是wrapper函数对象,未运行 9 print('run') 10 11 run() #run()=my_log(run())=wrapper() 此时调用并运行了wrapper()函数 12 13 # 运行结果如下 14 # decorator works 15 # run
1 def my_log(func): 2 def wrapper(): 3 print('decorator works') 4 func() 5 return wrapper #返回的只是wrapper函数对象,此时并没有运行 6 7 @my_log 8 def add(x,y): #add=my_log(add)=wrapper 返回的也是wrapper函数对象,未运行 9 print(x+y) 10 11 add(3,6) 12 #试图调用并运行wrapper() 13 14 #结果出错 15 #TypeError: wrapper() takes 0 positional arguments but 2 were given 16 # 原因是运行add(3,6)时传入了两个参数add(3,6)=my_log(add(3,6))=wrapper(3,6) 但wrapper()函数中并无参数接收
修改后的正确装饰器函数为
1 def my_log(func): 2 def wrapper(x,y): 3 print('decorator works') 4 func(x,y) 5 return wrapper #返回的只是wrapper函数对象,此时并没有运行 6 7 @my_log 8 def add(x,y): #add=my_log(add)=wrapper 返回的也是wrapper函数对象,未运行 9 print(x+y) 10 11 add(3,6) 12 13 # 运行结果如下 14 # decorator works 15 # 9
但并不是完善的装饰器函数
1 def my_log(func): 2 def wrapper(x,y): 3 print('decorator works') 4 func(x,y) 5 return wrapper #返回的只是wrapper函数对象,此时并没有运行 6 7 @my_log 8 def add(x,y): 9 print(x+y) 10 11 add(3,6) 12 13 @my_log 14 def run(): 15 print('run') 16 17 run() 18 19 # 但添加无参函数run()时又出现了错误 20 # TypeError: wrapper() missing 2 required positional arguments: 'x' and 'y' 21 # 因run()=my_log(run())=wrapper()未传给wrapper所必需的两个参数
此时需要修改装饰器中wrapper函数为非关键字可变参数和关键字可变参数来保证 无论是否有参数传入都正常运行
1 def my_log(func): 2 def wrapper(*args,**kwargs): 3 print('decorator works') 4 func(*args,**kwargs) 5 return wrapper #返回的只是wrapper函数对象,此时并没有运行 6 7 @my_log 8 def add(x,y): 9 print(x+y) 10 11 add(3,6) 12 13 @my_log 14 def run(): 15 print('run') 16 17 run() 18 19 # 运行结果如下 20 # decorator works 21 # 9 22 # decorator works 23 # run
但此时装饰器函数并未完全正确
1 def my_log(func): 2 def wrapper(*args,**kwargs): 3 print('decorator works') 4 func(*args,**kwargs) 5 return wrapper #返回的只是wrapper函数对象,此时并没有运行 6 7 @my_log 8 def add(x,y): 9 print(x+y) 10 11 @my_log 12 def run(): 13 print('run') 14 15 print(run.__name__) 16 print(add.__name__) 17 18 # 运行结果如下 19 # wrapper 20 # wrapper 21 #因run=my_log(run)=wrapper 偷偷修改了run方法和add方法的__name__属性
需要在装饰器函数内部添加@wraps来保证被装饰函数__name__属性不被修改
1 from functools import wraps 2 3 def my_log(func): 4 @wraps(func) 5 def wrapper(*args,**kwargs): 6 print('decorator works') 7 func(*args,**kwargs) 8 return wrapper #返回的只是wrapper函数对象,此时并没有运行 9 10 @my_log 11 def run(): # run=my_log(run)=wrapper 返回的是wrapper函数对象,未运行 12 print('running') 13 14 run() 15 # run()=my_log(run())=wrapper() 16 17 print("函数run的__name__是%s"%run.__name__) 18 print('-'*30) 19 20 @my_log # add=my_log(add)=wrapper 返回的也是wrapper函数对象 21 def add(a,b): 22 print(a+b) 23 24 add(3,4) 25 # add(3,4)=my_log(add(3,4))=wrapper(3,4) 26 print("函数add的__name__是%s"%add.__name__) 27 28 # 运行结果如下 29 # running 30 # 函数run的__name__是run 31 # ------------------------------ 32 # decorator works 33 # 7 34 # 函数add的__name__是add
最后总结:
1.装饰器中定义的函数要使用*args 和**kwargs 组合来接收任何可能被装饰函数的参数,在装饰器中的wrapper函数中执行原函数时需传入*args 和**kwargs
2.需使用functools.wraps在装饰器中wrapper函数前将wrapper函数用@wraps包裹,防止被装饰函数__name__属性被修改