一、概念:
装饰器常用于对已经上线的代码,不做任何变动的情况增加额外功能;装饰器本质上也是一个函数,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。
装饰器由:1:高阶函数,、2:内嵌函数、3:闭包 ;三个组成。
高阶函数:
def bar(): print("in the bar") def foo(func): print(func) rs = func() return rs foo(bar)
运行结果:
<function bar at 0x037914F8> in the bar
代码讲解:程序从上往下执行,定义bar和foo两个函数,执行foo(bar),将bar函数内存地址当做变量传给foo里的func, 此时打印func为bar函数的地址,即func()等价于bar(),且将结果赋值给rs, 并通过return返回。
内嵌函数:
def foo(): def bar(): rs = print("in the bar") bar() foo()
运行结果:
in the bar
代码讲解: 当程序运行到foo()时,foo()内部定义一个bar()函数,并且执行foo函数里的bar(),进行调用。
闭包:内部函数对外部函数作用域内的变量的引用(非全局变量),则称内部函数为闭包
def test(number): print("--1--") def test_in(number2): print("--3--") print(number+number2) print("--2--") return test_in ret = test(100) ret(1)运行结果:
--1-- --2-- --3-- 101
代码讲解:程序在运行时,先定义test这个函数,然后执行ret = test(100) ;此时将100传递给number, 而test()这个函数,返回test_in,返回的结果是test_in()这个函数的地址, 即ret = test(100),只是将ret指向于test_in()函数的内存地址,并给number赋值为100; 程序再向下执行,ret() 调用test_in()这个函数,把1赋值给number2。
其中以下代码就称之为闭包
def test_in(number2): print("--3--") print(number+number2)
装饰器: 高阶函数+内嵌函数+闭包 ==> 装饰器,
装饰器满足条件:1、不对原代码进行修改;2、不对调用方式进行更改;3、再原功能上实现功能添加
例1:简单装饰器
原有代码:
def test(): print('---test---') test()
此时使用装饰器对代码进行装饰,且不更改原有的代码及调用方式
def make1(func): def inner(): print("--装饰test---") func() return inner @make1 def test(): print('---test---') test()
运行结果:
--装饰test--- ---test---
代码讲解: 程序中@make1 即为装饰器,使用@make1对test()这个函数进行装饰(需要对某个函数进行装饰,只需在函数上方使用@函数名即可),@make1 等价于,test = make1(test)。 程序从上往下执行,先定义make1函数,再定义test函数,随后运行@make1即(test = make1(test)),此时调用make1()通过return返回inner函数的内存地址,且把test函数的内存地址传递给func,再次把结果赋值给test这个变量,这个时候的test相当于是一个新的内存地址;再由test()来进行调用。且装饰器是执行到@时就已进行装饰,而不是等到调用时才执行
例2:有返回值的装饰器
def test2(func): print("---test2---") def test3(): message1 = func() return message1 return test3 @test2 def test1(): print("---test1---") return "Test_Rerurn" message2 = test1() print(message2)
运行结果:
---test2--- ---test1--- Test_Rerurn
代码讲解:通过@test2装饰器对test1进行装饰,运行test2()这个函数,打印“--test2--”把test1内存地址赋值给func,再通过return返回test3函数内存地址并赋值给新变量test1,再次通过test1()调用test3(),运行函数里的func(),执行最原始的test1代码,打印“--test1--”并把返回的“TestReturn”赋值给message1,再次返回message1, 再把返回的结果用message2保存,最后再打印出来。
例3:多个装饰器
def makeBold(fn): def wrapped(): print("----1---") return "<b>" + fn() + "</b>" return wrapped def makeItalic(fn): def wrapped(): print("----2---") return "<i>" + fn() + "</i>" return wrapped @makeBold @makeItalic def test3(): print("----3---") return "hello world-3" ret = test3() print(ret)
运行结果:
----1--- ----2--- ----3--- <b><i>hello world-3</i></b>
代码讲解: 在装饰器中可以使用多个装饰器,程序从上往下依次执行。
例4:带参数的装饰器
def func(functionName): print("---func---1---") def func_in(*args, **kwargs): print("---func_in---1---") functionName(*args, **kwargs) print("---func_in---2---") print("---func---2---") return func_in @func def test(a, b, c): print("----test-a=%d,b=%d,c=%d---"%(a,b,c)) test(11,22,33)
运行结果:
---func---1--- ---func---2--- ---func_in---1--- ----test-a=11,b=22,c=33--- ---func_in---2---
代码讲解:通过@func装饰器对test进行装饰,将test函数的内存地址传递给functionName,返回func_in的内存地址返回,用新变量test保存,再来执行test(11,22,33),此时会调用func_in这个函数, 为了保证函数通用行,test传递的形参,fun_in需要用不定长参数来保存,其中functionName()执行时,也需要携带不定长参数。
例5:装饰器实用:
import time user,passwd = 'alex','abc123' def auth(auth_type): #print("auth func:",auth_type) def outer_wrapper(func): def wrapper(*args, **kwargs): #print("wrapper func args:", *args, **kwargs) if auth_type == "local": username = input("Username:").strip() password = input("Password:").strip() if user == username and passwd == password: print("\033[32;1mUser has passed authentication\033[0m") res = func(*args, **kwargs) print("---after authenticaion ") return res else: exit("\033[31;1mInvalid username or password\033[0m") elif auth_type == "ldap": print("PASS") return wrapper return outer_wrapper def index(): print("welcome to index page") @auth(auth_type="local") # home = wrapper() def home(): print("welcome to home page") return "from home" @auth(auth_type="ldap") def bbs(): print("welcome to bbs page") index() home() bbs()
运行结果:
welcome to index page Username:alex Password:abc123 User has passed authentication welcome to home page PASS大概讲解:该例子主体为有index函数、home函数、bbs函数, 先需要对home已及bbs进行访问验证,而验证的方式有两种,一种为local本地验证,一种为ldap验证,通过装饰器来对函数进行装饰,并且通过装饰器传递的值不同,用来进行验证方式的判断