定义
本质就是函数,功能是装饰其它函数,其实就是给其它函数添加附加功能。
原则
1、不能修改被装饰的函数的源代码
2、不能修改被装饰的函数的调用方式
其实就是当你用装饰器函数装饰其它函数的时候,被装饰的函数被装饰之前的调用方式不会有任何区别。
实现装饰器的知识储备
1、函数即 “变量”
2、高阶函数
3、嵌套函数
高阶函数+嵌套函数 == 装饰器
高阶函数
什么是高阶函数?
1、把一个函数名当做实参传给另外一个函数
2、返回值中包含函数名
下面看例子
比如51cto网站上有以下版块
def blog(): print('欢迎来到 51cto 博客专区!') def forum(): print('欢迎来到 51cto 论坛专区!') def college(): print('欢迎来到 51cto 学院专区!') blog() forum() college()
初期,这几个版块都可以免费访问,但是有些版块涉及到一些商业用途。如学院版块,里面有大量的视频教程可以商用。就需要付费,才能观看这些视频。为了实现这个需求,可以考虑让其进行用户认证,认证通过后,再判定这个用户是否是VIP付费会员就可以了。这个需求看似很简单,因为要对多个板块进行认证,应该把认证功能提取出来单独写个模块,然后每个模块调用就可以了。看下面代码:
user_status =False #用户登录状态 username,password = 'tbb','123' #假设这是DB里存的用户信息 def login(): global user_status if user_status == False: _username = input('输入用户名:') _password = input('输入密码:') if _username == username and _password == password: user_status = True #登录成功就把状态改为True return True else: print('认证失败!') return False else: return True def blog(): print('欢迎来到 51cto 博客专区!') def forum(): status = login() #执行验证 if status: print('欢迎来到 51cto 论坛专区!') def college(): status = login() #执行验证 if status: print('欢迎来到 51cto 学院专区!') blog() forum() college()
上面的代码虽然实现了功能,但是需要更改需要加认证的各个模块的代码,这直接违反了软件开发中的一个 “开放-封闭” 的原则。简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:
- 封闭:已经实现的代码功能块不应该被修改
- 开放:对现有功能的扩展开放
要实现这个需求,那么就要用到高阶函数了,就是把一个函数当做一个参数传给另外一个函数,看下面的代码:
user_status = False # 用户登录状态 username, password = 'tbb', '123' # 假设这是DB里存的用户信息 def login(func): #把要执行的模块从这里传进来 global user_status if user_status == False: _username = input('输入用户名:') _password = input('输入密码:') if _username == username and _password == password: user_status = True # 登录成功就把状态改为True #return True else: print('认证失败!') #return False if user_status == True: #return True func() #只要验证通过了,就调用相应功能 def blog(): print('欢迎来到 51cto 博客专区!') def forum(): #status = login() # 执行验证 #if status: print('欢迎来到 51cto 论坛专区!') def college(): #status = login() # 执行验证 #if status: print('欢迎来到 51cto 学院专区!') blog() #forum() login(forum) #需要验证就调用login,把需要验证的功能当做一个参数传给login #college() login(college)
上面的代码虽然在不改变源功能代码的前提下,给功能加上了验证,但是却改变了调用方式。想一想,现在每个需要认证的模块,都必须调用你的login()方法,并把自己的函数名传给你,这和之前的调用方式完全不一样。试想一下,在生产环境中,如果有100个模块需要认证,那这100个模块都得更改调用方式,这么多模块肯定不止是一个人写的,让每个人再去修改调用方式才能加上认证,这样会被骂死的。
要实现这种需求,用嵌套函数加上高阶函数就可以实现,看下面的代码:
user_status = False # 用户登录状态 username, password = 'tbb', '123' # 假设这是DB里存的用户信息 def login(func): #把要执行的模块从这里传进来 def inner(): #再定义一层函数 global user_status if user_status == False: _username = input('输入用户名:') _password = input('输入密码:') if _username == username and _password == password: user_status = True # 登录成功就把状态改为True #return True else: print('认证失败!') #return False if user_status == True: #return True func() return inner #用户调用login时,返回的是inner的内存地址 def blog(): print('欢迎来到 51cto 博客专区!') def forum(): print('欢迎来到 51cto 论坛专区!') def college(): print('欢迎来到 51cto 学院专区!') blog() forum = login(forum) #调用login函数,返回inner的内存地址,再赋值给forum变量,即forum = inner的内存地址 forum() #此时,执行forum()其实就是执行了inner(),inner函数中的func()才是装饰器要装饰的forum函数,即func()就是forum() #login(forum) college = login(college) college() #login(college)
上面代码中的使用装饰器的方法并不是最好的,因为我们是执行了forum = login(forum),然后执行forum(),其实Python中给我们提供了一种方法,即在要装饰的函数上面添加@login,执行@login其实就是执行forum = login(forum),并且上面这种方式,还存在一个问题,就是当被装饰的函数有参数需要传递的时候,就不能用了,修改后的代码如下:
user_status = False # 用户登录状态 username, password = 'tbb', '123' # 假设这是DB里存的用户信息 def login(func): #把要执行的模块从这里传进来 def inner(*args,**kwargs): #再定义一层函数 global user_status if user_status == False: _username = input('输入用户名:') _password = input('输入密码:') if _username == username and _password == password: user_status = True # 登录成功就把状态改为True #return True else: print('认证失败!') #return False if user_status == True: #return True func(*args,**kwargs) return inner #用户调用login时,返回的是inner的内存地址 def blog(): print('欢迎来到 51cto 博客专区!') @login #就相当于 forum = login(forum) def forum(name): print('欢迎来到 51cto 论坛专区! %s' %name) @login #就相当于 college = login(college) def college(name): print('欢迎来到 51cto 学院专区! %s' %name) blog() forum('tbb') college('tbb')
上述代码还有一个小问题,就是如果被装饰的函数存在return返回值得时候,也是无法显示返回的数据,看下图
修改后的代码如下所示:
user_status = False # 用户登录状态 username, password = 'tbb', '123' # 假设这是DB里存的用户信息 def login(func): #把要执行的模块从这里传进来 def inner(*args,**kwargs): #再定义一层函数 global user_status if user_status == False: _username = input('输入用户名:') _password = input('输入密码:') if _username == username and _password == password: user_status = True # 登录成功就把状态改为True else: print('认证失败!') if user_status == True: #return True res = func(*args,**kwargs) #把forum()执行后的返回值赋值给res return res return inner #用户调用login时,返回的是inner的内存地址 def blog(): print('欢迎来到 51cto 博客专区!') @login #就相当于 forum = login(forum) def forum(name): print('欢迎来到 51cto 论坛专区! %s' %name) return 'from forum' @login #就相当于 college = login(college) def college(name): print('欢迎来到 51cto 学院专区! %s' %name) blog() print(forum('tbb')) college('tbb')
但是上面的代码还不是最完整的,有一种情况上述的代码无法使用,就是在做用户认证的时候,如果有多个板块,要用不同的认证方式,即论坛专区用本地认证,学院专区用ldap来认证。即代码应该为:
user_status = False # 用户登录状态 username, password = 'tbb', '123' # 假设这是DB里存的用户信息 def login(auth_type): #把要执行的模块从这里传进来 def auth(func): def inner(*args,**kwargs): #再定义一层函数 print('认证方式为: 33[31;1m%s 33[0m'%auth_type) global user_status if user_status == False: _username = input('输入用户名:') _password = input('输入密码:') if _username == username and _password == password: user_status = True # 登录成功就把状态改为True else: print('认证失败!') if user_status == True: res = func(*args,**kwargs) #把forum()执行后的返回值赋值给res return res return inner #用户调用auth时,返回的是inner的内存地址 return auth #用户调用login时,返回的是auth的内存地址 def blog(): print('欢迎来到 51cto 博客专区!') @login(auth_type='local') #当添加@login(auth_type='local')的时候是先执行login(auth_type='local'),其实就是执行login方法,这时候返回auth的内存地址,即@login(auth_type='local')变成了@auth,而@auth就相当于forum = auth(forum),即forum = inner,这样当后面调用forum()其实就是调用inner() def forum(name): print('欢迎来到 51cto 论坛专区! %s' %name) return 'from forum' @login(auth_type='ldap') def college(name): print('欢迎来到 51cto 学院专区! %s' %name) blog() print(forum('tbb')) college('tbb')