装饰器
装饰器本质是函数,是用来装饰其他函数,顾名思义就是,为其他的函数添加附件功能的。
一、装饰器原则:
-
不能修改被装饰函数的源代码
-
不能修改被装饰函数的调用方式
def logging(): print("logging...") #正确写法,没有修改源码 def test1(): pass #错误写法,不能修改源码 def test1(): pass logging() # 调用方式,也不能被修改 test1()
二、装饰器知识:
- 函数即"变量"
- 高阶函数+嵌套函数 =》装饰器
1、函数即”变量“
python的内存机制,看如下代码:
#变量 x = 1 #函数 def test(): pass
在内存图中是这样表示的:
x、test 是变量名,保存在栈内存中,1、函数体 保存在堆内存中
2、高阶函数+嵌套函数 =》装饰器
装饰器实现过程:
第一步:原始代码
def home(): print("---首页----") def TV(): print("----TV----") def music() print("---music-----")
第二步:想给部分模块加个登陆认证
user_status = False #用户登录了就把这个改成True def login(): _username = "ABC" #假装这是DB里存的用户信息 _password = "12345" #假装这是DB里存的用户信息 global user_status if user_status == False: username = input("user:") password = input("pasword:") if username == _username and password == _password: print("welcome login....") user_status = True else: print("wrong username or password!") else: print("用户已登录,验证通过...") def home(): print("---首页----") def TV(): login() #执行前加上验证 print("----TV----") def music(): print("----music----")
虽然这样实现了认证功能,但是修改了被装饰函数的源代码,违背了装饰器的原则”不能修改被装饰函数的源代码“
第三步:代码改进,使用高阶函数理念,把函数名当参数传递给认证函数login,这样可以不修改被装饰函数源代码的情况下完成登陆认证
user_status = False #用户登录了就把这个改成True def login(func): _username = "ABC" #假装这是DB里存的用户信息 _password = "12345" #假装这是DB里存的用户信息 global user_status if user_status == False: username = input("user:") password = input("pasword:") if username == _username and password == _password: print("welcome login....") user_status = True else: print("wrong username or password!") if user_status == True: print("用户已登录,验证通过...") func() #只要验证通过了,就调用相应功能 def home(): print("---首页----") def TV(): print("----TV----") def music(): print("----music----") login(TV) #需要验证就调用 login,把需要验证的功能 当做一个参数传给login
虽然这样可以不修改被装饰函数源代码的情况下完成登陆认证,但是违背了装饰器原则”修改了被装饰函数的调用方式“,本来被装饰函数只需要TV()就可调用,现在变成了login(TV)
第四步:代码改进,使用匿名函数理念,将login(TV)变成 TV = login(TV) ,将函数当成值,赋值给变量名TV,跟关键字def 重新定义了TV是一样的效果,不过这样还有一个问题, TV = login(TV)这个赋值过程中,就把函数TV给调用了,用户自己还没有调用,就自己自动调用肯定是不对的,这个时候需要用到嵌套函数的理念了,在认证函数login里面的再定义一个新函数login_inner,在login函数return(返回)login_inner函数名(对是return login_inner, 不是return login_inner(), 因为return 函数名 返回的是函数在栈内容的内存地址,return 函数名+() 返回的是该函数的执行结果) 这样在TV = login(TV)赋值的时候,TV赋值的就不是 login(TV)的执行结果了,赋值的值是login_inner的内存地址,等用户再调用的时候 就是TV(),这样就没有改变被装饰函数的调用方式了。
user_status = False #用户登录了就把这个改成True def login(func): #在login 里面增加一个嵌套函数,保证tv = login(tv)的时候 不会自己自动调用 tv函数 def login_inner(): _username = "ABC" #假装这是DB里存的用户信息 _password = "12345" #假装这是DB里存的用户信息 global user_status if user_status == False: username = input("user:") password = input("pasword:") if username == _username and password == _password: print("welcome login....") user_status = True else: print("wrong username or password!") if user_status == True: print("用户已登录,验证通过...") func() #只要验证通过了,就调用相应功能 return login_inner # return 函数名 返回的是函数在栈内容的内存地址,return 函数名+() 返回的是该函数的执行结果 def home(): print("---首页----") def TV(): print("----TV----") def music(): print("----music----") home() #login(TV) #改成下面的方式,这样就不会改变调用方式了 TV = login(TV) TV()
每次使用装饰器都这么麻烦?需要使用匿名函数重新赋值再调用?
第五步:其实可以把TV =login(TV),的赋值过程简化成在被装饰函数前@login 就好了,如下列代码:
user_status = False #用户登录了就把这个改成True def login(func): #在login 里面增加一个嵌套函数,保证tv = login(tv)的时候 不会自己自动调用 tv函数 def login_inner(): _username = "ABC" #假装这是DB里存的用户信息 _password = "12345" #假装这是DB里存的用户信息 global user_status if user_status == False: username = input("user:") password = input("pasword:") if username == _username and password == _password: print("welcome login....") user_status = True else: print("wrong username or password!") if user_status == True: print("用户已登录,验证通过...") func() #只要验证通过了,就调用相应功能 return login_inner # return 函数名 返回的是函数在栈内容的内存地址,return 函数名+() 返回的是该函数的执行结果 def home(): print("---首页----") @login def TV(): print("----TV----") @login def music(): print("----music----")
装饰器装饰没有参数的函数:
import time #定义装饰器函数 def timmer(func): # 把test1这个函数名作为参数传递进来 func=test1 #定义装饰器中的内置函数 def deco(): start_time = time.time() func() #相当于运行test1() stop_time = time.time() print("the func run time is %s"%(stop_time-start_time)) return deco #装饰test1函数 @timmer # 相当于test1 = timmer(test1) def test1(): time.sleep(3) print("in the test1") #直接执行test1函数 test1() #输出 in the test1 the func run time is 3.0002999305725098
装饰器装饰带有参数的函数:
import time def timmer(func): #timmer(test1) func=test1 # 因为之前返回的是这个嵌套数的内存地址,如果这个嵌套函数不传入参数#的话,里面的func,就是被装饰函数本身就没有参数,这样就会报错 def deco(*args,**kwargs): #传入非固定参数 start_time = time.time() func(*args,**kwargs) #传入非固定参数 stop_time = time.time() print("the func run time is %s"%(stop_time-start_time)) return deco #不带参数 @timmer # 相当于test1 = timmer(test1) def test1(): time.sleep(3) print("in the test1") #带参数 @timmer def test2(name,age): print("name:%s,age:%s"%(name,age)) #调用 test1() test2("zhangqigao",22) #输出 #test1 in the test1 the func run time is 3.0010883808135986 #test2 name:zhangqigao,age:22 the func run time is 0.0 #test2
装饰器装饰有返回值的函数:
def timmer(func): #timmer(test1) func=test1 def deco(*args,**kwargs): res = func(*args,**kwargs) #这边传入函数结果赋给res return res # 返回res return deco @timmer def test1(): # test1 = timmer(test1) print("in the test1") return "from the test1" #执行函数test1有返回值 res = test1() print(res) #输出 in the test1 from the test1
装饰器本身带有参数:
#本地验证 user,passwd = "zhangqigao","abc123" def auth(auth_type): #传递装饰器的参数 print("auth func:",auth_type) def outer_wrapper(func): # 将被装饰的函数作为参数传递进来 def wrapper(*args,**kwargs): #将被装饰函数的参数传递进来 print("wrapper func args:",*args,**kwargs) username = input("Username:").strip() password = input("Password:").strip() if auth_type == "local": if user == username and passwd == password: print("