写python中的装饰器
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()=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)=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()=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()=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)=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 from functools import wraps 2 3 4 #***************错误************************* 5 def login_required(func): 6 @wraps(func) 7 def wrapper(*args,**kwargs): 8 print('hello world') 9 func(*args,**kwargs) 10 return wrapper 11 12 @login_required 13 def index(): 14 return 'aaaaaaaaaaa' 15 16 a=index() 17 print(a) 18 #index=login_required()=wrapper 19 #index()=wrapper() 20 # def wrapper(*args,**kwargs): 21 # print('hello world') 22 # index() 此处只是调用index函数得到'aaaaaaaaaaa' 但wrapper函数并未返回该值 23 24 # hello world 25 # None 可见wrapper()调用index()函数并未返回'aaaaaaaaaaa' 26 27 #**************正确******************** 28 def login_required2(func): 29 @wraps(func) 30 def wrapper(*args,**kwargs): 31 print('hello world') 32 return func(*args,**kwargs) 33 return wrapper 34 35 @login_required2 36 def index(): 37 return 'aaaaaaaaaaa' 38 39 print('-'*20) 40 a=index() 41 print(a) 42 43 44 45 #输出结果如下 46 #-------------------- 47 # hello world 48 # aaaaaaaaaaa
最后总结:
1.装饰器中定义的函数要使用*args 和**kwargs 组合来接收任何可能被装饰函数的参数,在装饰器中的wrapper函数中执行原函数时需传入*args 和**kwargs
2.需使用functools.wraps在装饰器中wrapper函数前将wrapper函数用@wraps包裹,防止被装饰函数__name__属性被修改
以下来自 CS实验室 大佬喵的 https://mp.weixin.qq.com/s?timestamp=1531273713&src=3&ver=1&signature=S-vLsC7yG6GltzbaRovEtemNFSFg3Ps*AQnH1hc3E7-huMUuZbG-i3m0c-8pkEihTO1UIA9wF4Ze8tlMKitOtFb8-eDjsQhYq7KDFDtPZpcotQikbQg8DRhzdgQHArojOFIjBhPb0wAnLzZ2hGn5PXYI4HjRy5CiEpNwr1ii09Y=
1 def red_oil(func): 2 print("ready to paint!") 3 4 def red_wall_func(): 5 print("red wall!") 6 7 return red_wall_func 8 9 10 @red_oil 11 def wall(): 12 print("wall!") 13 14 if __name__=="__main__": 15 print("start painting!!!")
执行结果如下:
1 ready to paint!
2 start painting!!!
1 def red_oil(func): 2 print("ready to paint!") 3 4 def red_wall_func(): 5 print("red wall!") 6 7 return red_wall_func 8 9 10 @red_oil 11 def wall(): 12 print("wall!") 13 14 if __name__=="__main__": 15 print("start painting!!!") 16 wall()
执行结果如下:
1 ready to paint!
2 start painting!!!
3 red wall!
装饰器在被装饰的函数被定义时立即执行,被装饰的函数在运行时才执行。