什么是开放封闭原则?有的同窗问开放,封闭这是两个反义词这还能组成一个原则么?这不先后矛盾么?其实不矛盾。开放封闭原则是分状况讨论的。python
咱们的软件一旦上线以后(好比你的软件主要是多个函数组成的),那么这个软件对功能的扩展应该是开放的,好比你的游戏一直在迭代更新,推出新的玩法,新功能。可是对于源代码的修改是封闭的。你就拿函数举例,若是你的游戏源代码中有一个函数是闪躲的功能,那么你这个函数确定是被多个地方调用的,好比对方扔雷,对方开枪,对方用刀,你都会调用你的闪躲功能,那么若是你的闪躲功能源码改变了,或者调用方式改变了,当对方发起相应的动做,你在调用你的闪躲功能,就会发生问题。因此,开放封闭原则具体具体定义是这样:面试
1.对扩展是开放的数组
咱们说,任何一个程序,不可能在设计之初就已经想好了全部的功能而且将来不作任何更新和修改。因此咱们必须容许代码扩展、添加新功能。微信
2.对修改是封闭的网络
就像咱们刚刚提到的,由于咱们写的一个函数,颇有可能已经交付给其余人使用了,若是这个时候咱们对函数内部进行修改,或者修改了函数的调用方式,颇有可能影响其余已经在使用该函数的用户。OK,理解了开封封闭原则以后,咱们聊聊装饰器。闭包
什么是装饰器?从字面意思来分析,先说装饰,什么是装饰? 装饰就是添加新的,好比你家刚买的房子,下一步就是按照本身的喜欢的方式设计,进行装修,装饰,地板,墙面,家电等等。什么是器?器就是工具,也是功能,那装饰器就好理解了:就是添加新的功能。app
好比我如今不会飞,怎么才能让我会飞?给我加一个翅膀,我就能飞了。那么你给我加一个翅膀,它会改变我原来的行为么?我以前的吃喝拉撒睡等生活方式都不会改变。它就是在我原来的基础上,添加了一个新的功能。函数
今天咱们讲的装饰器(装修,翅膀)是以功能为导向的,就是一个函数。工具
被装饰的对象:好比毛坯房,我本人,其实也是一个函数。学习
因此装饰器最终最完美的定义就是:在不改变原被装饰的函数的源代码以及调用方式下,为其添加额外的功能。
接下来,咱们经过一个例子来为你们讲解这个装饰器:
需求介绍:你如今xx科技有限公司的开发部分任职,领导给你一个业务需求让你完成:让你写代码测试小明同窗写的函数的执行效率。
def index(): print('欢迎访问博客园主页')
版本1:
需求分析:你要想测试此函数的执行效率,你应该怎么作?应该在此函数执行前记录一个时间, 执行完毕以后记录一个时间,这个时间差就是具体此函数的执行效率。那么执行时间如何获取呢? 能够利用time模块,有一个time.time()功能。
import time print(time.time())
此方法返回的是格林尼治时间,是此时此刻距离1970年1月1日0点0分0秒的时间秒数。也叫时间戳,他是一直变化的。因此要是计算shopping_car的执行效率就是在执行先后计算这个时间戳的时间,而后求差值便可。
import time def index(): print('欢迎访问博客园主页') start_time = time.time() index() end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}')
因为index函数只有一行代码,执行效率太快了,因此咱们利用time模块的一个sleep模拟一下
import time def index(): time.sleep(2) # 模拟一下网络延迟以及代码的效率 print('欢迎访问博客园主页') start_time = time.time() index() end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}')
版本1分析:你如今已经完成了这个需求,可是有什么问题没有? 虽然你只写了四行代码,可是你完成的是一个测试其余函数的执行效率的功能,若是让你测试一下,小张,小李,小刘的函数效率呢? 你是否是全得复制:
import time def index(): time.sleep(2) # 模拟一下网络延迟以及代码的效率 print('欢迎访问博客园首页') def home(name): time.sleep(3) # 模拟一下网络延迟以及代码的效率 print(f'欢迎访问{name}主页') start_time = time.time() index() end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') start_time = time.time() home('太白') end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') ......
重复代码太多了,因此要想解决重复代码的问题,怎么作?咱们是否是学过函数,函数就是以功能为导向,减小重复代码,好咱们继续整改。
版本2:
import time def index(): time.sleep(2) # 模拟一下网络延迟以及代码的效率 print('欢迎访问博客园主页') def inner(): start_time = time.time() index() end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') inner()
可是你这样写也是有问题的,你虽然将测试功能的代码封装成了一个函数,可是这样,你只能测试小明同窗的的函数index,你要是测试其余同事的函数呢?你怎么作?
import time def index(): time.sleep(2) # 模拟一下网络延迟以及代码的效率 print('欢迎访问博客园主页') def home(name): time.sleep(3) # 模拟一下网络延迟以及代码的效率 print(f'欢迎访问{name}主页') def inner(): start_time = time.time() index() home('太白') end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') timer()
你要是像上面那么作,每次测试其余同事的代码还须要手动改,这样是否是太low了?因此如何变成动态测试其余函数?咱们是否是学过函数的传参?可否将被装饰函数的函数名做为函数的参数传递进去呢?
版本3:
import time def index(): time.sleep(2) # 模拟一下网络延迟以及代码的效率 print('欢迎访问博客园主页') def home(name): time.sleep(3) # 模拟一下网络延迟以及代码的效率 print(f'欢迎访问{name}主页') def timmer(func): # func == index 函数 start_time = time.time() func() # index() end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') timmer(index)
这样我将index函数的函数名做为参数传递给timmer函数,而后在timmer函数里面执行index函数,这样就变成动态传参了。好,大家如今将版本3的代码快速练一遍。 你们练习完了以后,发现有什么问题么? 对比着开放封闭原则说: 首先,index函数除了完成了本身以前的功能,还增长了一个测试执行效率的功能,对不?因此也符合开放原则。 其次,index函数源码改变了么?没有,可是执行方式改变了,因此不符合封闭原则。 原来如何执行? index() 如今如何执行? inner(index),这样会形成什么问题? 假如index在你的项目中被100处调用,那么这相应的100处调用我都得改为inner(index)。 很是麻烦,也不符合开放封闭原则。
版本4:实现真正的开放封闭原则:装饰器。
这个也很简单,就是咱们昨天讲过的闭包,只要你把那个闭包的执行过程整清楚,那么这个你想不会都难。
import time def index(): time.sleep(2) # 模拟一下网络延迟以及代码的效率 print('欢迎访问博客园主页') def home(name): time.sleep(3) # 模拟一下网络延迟以及代码的效率 print(f'欢迎访问{name}主页')
你将上面的inner函数在套一层最外面的函数timer,而后将里面的inner函数名做为最外面的函数的返回值,这样简单的装饰器就写好了,一点新知识都没有加,这个若是不会就得多抄几遍,而后理解代码。
def timer(func): # func = index def inner(): start_time = time.time() func() end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') return inner # f = timer(index) # f()
咱们分析一下,代码,代码执行到这一行:f = timer(index) 先执行谁?看见一个等号先要执行等号右边, timer(index) 执行timer函数将index函数名传给了func形参。内层函数inner执行么?不执行,inner函数返回 给f变量。因此咱们执行f() 就至关于执行inner闭包函数。 f(),这样既测试效率又执行了原函数,有没有问题?固然有啦!!版本4你要解决原函数执行方式不改变的问题,怎么作? 因此你能够把 f 换成 index变量就完美了! index = timer(index) index()带着同窗们将这个流程在执行一遍,特别要注意 函数外面的index实际是inner函数的内存地址而不是index函数。让学生们抄一遍,理解一下,这个timer就是最简单版本装饰器,在不改变原index函数的源码以及调用方式前提下,为其增长了额外的功能,测试执行效率。
你如今这个代码,完成了最第一版的装饰器,可是仍是不够完善,由于你被装饰的函数index可能会有返回值,若是有返回值,你的装饰器也应该不影响,开放封闭原则嘛。可是你如今设置一下试试:
import time def index(): time.sleep(2) # 模拟一下网络延迟以及代码的效率 print('欢迎访问博客园主页') return '访问成功' def timer(func): # func = index def inner(): start_time = time.time() func() end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') return inner index = timer(index) print(index()) # None
加上装饰器以后,他的返回值为None,为何?由于你如今的index不是函数名index,这index实际是inner函数名。因此index() 等同于inner() 你的 '访问成功'返回值应该返回给谁?应该返回给index,这样才作到开放封闭,实际返回给了谁?实际返回给了func,因此你要更改一下你的装饰器代码,让其返回给外面的index函数名。 因此:你应该这么作:
def timer(func): # func = index def inner(): start_time = time.time() ret = func() end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') return ret return inner index = timer(index) # inner print(index()) # print(inner())
借助于内层函数inner,你将func的返回值,返回给了inner函数的调用者也就是函数外面的index,这样就实现了开放封闭原则,index返回值,确实返回给了'index'。
让同窗们;练习一下。
到目前为止,你的被装饰函数仍是没有传参呢?按照咱们的开放封闭原则,加不加装饰器都不能影响你被装饰函数的使用。因此咱们看一下。
import time def index(): time.sleep(2) # 模拟一下网络延迟以及代码的效率 print('欢迎访问博客园主页') return '访问成功' def home(name): time.sleep(3) # 模拟一下网络延迟以及代码的效率 print(f'欢迎访问{name}主页') def timer(func): # func = index def inner(): start_time = time.time() func() end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') return inner # 要想timer装饰home函数怎么作? home = timer(home) home('太白')
上面那么作,显然报错了,为何? 你的home这个变量是谁?是inner,home('太白')实际是inner('太白')可是你的'太白'这个实参应该传给谁? 应该传给home函数,实际传给了谁?实际传给了inner,因此咱们要经过更改装饰器的代码,让其将实参'太白'传给home.
import time def index(): time.sleep(2) # 模拟一下网络延迟以及代码的效率 print('欢迎访问博客园主页') return '访问成功' def home(name): time.sleep(3) # 模拟一下网络延迟以及代码的效率 print(f'欢迎访问{name}主页') def timer(func): # func = home def inner(name): start_time = time.time() func(name) # home(name) == home('太白') end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') return inner # 要想timer装饰home函数怎么作? home = timer(home) home('太白')
这样你就实现了,还有一个小小的问题,如今被装饰函数的形参只是有一个形参,若是要是多个怎么办?有人说多少个我就写多少个不就好了,那不行呀,你这个装饰器能够装饰N多个不一样的函数,这些函数的参数是不统一的。因此你要有一种能够接受不定数参数的形参接受他们。这样,你就要想到*args,**kwargs。
import time def index(): time.sleep(2) # 模拟一下网络延迟以及代码的效率 print('欢迎访问博客园主页') return '访问成功' def home(name,age): time.sleep(3) # 模拟一下网络延迟以及代码的效率 print(name,age) print(f'欢迎访问{name}主页') def timer(func): # func = home def inner(*args,**kwargs): # 函数定义时,*表明聚合:因此你的args = ('太白',18) start_time = time.time() func(*args,**kwargs) # 函数的执行时,*表明打散:因此*args --> *('太白',18)--> func('太白',18) end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') return inner home = timer(home) home('太白',18)
这样利用*的打散与聚合的原理,将这些实参经过inner函数的中间完美的传递到给了相应的形参。
好将上面的代码在敲一遍。
代码优化:语法糖
根据个人学习,咱们知道了,若是想要各给一个函数加一个装饰器应该是这样:
def home(name,age): time.sleep(3) # 模拟一下网络延迟以及代码的效率 print(name,age) print(f'欢迎访问{name}主页') def timer(func): # func = home def inner(*args,**kwargs): start_time = time.time() func(*args,**kwargs) end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') return inner home = timer(home) home('太白',18)
若是你想给home加上装饰器,每次执行home以前你要写上一句:home = timer(home)这样你在执行home函数 home('太白',18) 才是真生的添加了额外的功能。可是每次写这一句也是很麻烦。因此,Python给咱们提供了一个简化机制,用一个很简单的符号去代替这一句话。
def timer(func): # func = home def inner(*args,**kwargs): start_time = time.time() func(*args,**kwargs) end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') return inner @timer # home = timer(home) def home(name,age): time.sleep(3) # 模拟一下网络延迟以及代码的效率 print(name,age) print(f'欢迎访问{name}主页') home('太白',18)
你看此时我调整了一下位置,你要是不把装饰器放在上面,timer是找不到的。home函数若是想要加上装饰器那么你就在home函数上面加上@home,就等同于那句话 home = timer(home)。这么作没有什么特殊意义,就是让其更简单化,好比你在影视片中见过野战军的做战时因为不方便说话,用一些简单的手势表明一些话语,就是这个意思。
至此标准版的装饰器就是这个样子:
def wrapper(func): def inner(*args,**kwargs): '''执行被装饰函数以前的操做''' ret = func '''执行被装饰函数以后的操做''' return ret return inner
这个就是标准的装饰器,彻底符合代码开放封闭原则。这几行代码必定要背过,会用。
此时咱们要利用这个装饰器完成一个需求:简单版模拟博客园登陆。 此时带着学生们看一下博客园,说一下需求: 博客园登录以后有几个页面,diary,comment,home,若是我要访问这几个页面,必须验证我是否已登陆。 若是已经成功登陆,那么这几个页面我均可以无阻力访问。若是没有登陆,任何一个页面都不能够访问,我必须先登陆,登陆成功以后,才能够访问这个页面。咱们用成功执行函数模拟做为成功访问这个页面,如今写三个函数,写一个装饰器,实现上述功能。
def auth(): pass def diary(): print('欢迎访问日记页面') def comment(): print('欢迎访问评论页面') def home(): print('欢迎访问博客园主页') 答案: login_status = { 'username': None, 'status': False, } def auth(func): def inner(*args,**kwargs): if login_status['status']: ret = func() return ret username = input('请输入用户名:').strip() password = input('请输入密码:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret return inner @auth def diary(): print('欢迎访问日记页面') @auth def comment(): print('欢迎访问评论页面') @auth def home(): print('欢迎访问博客园主页') diary() comment() home()
咱们看,装饰器其实就是一个闭包函数,再说简单点就是两层的函数。那么是函数,就应该具备函数传参功能。
login_status = { 'username': None, 'status': False, } def auth(func): def inner(*args,**kwargs): if login_status['status']: ret = func() return ret username = input('请输入用户名:').strip() password = input('请输入密码:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret return inner
你看我上面的装饰器,不要打开,他能够不可在套一层:
def auth(x): def auth2(func): def inner(*args,**kwargs): if login_status['status']: ret = func() return ret username = input('请输入用户名:').strip() password = input('请输入密码:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret return inner return auth
举例说明:抖音:绑定的是微信帐号密码。 皮皮虾:绑定的是qq的帐号密码。 你如今要完成的就是你的装饰器要分状况去判断帐号和密码,不一样的函数用的帐号和密码来源不一样。 可是你以前写的装饰器只能接受一个参数就是函数名,因此你写一个能够接受参数的装饰器。
def auth2(func): def inner(*args, **kwargs): if login_status['status']: ret = func() return ret if 微信: username = input('请输入用户名:').strip() password = input('请输入密码:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret elif 'qq': username = input('请输入用户名:').strip() password = input('请输入密码:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret return inner @auth2 def jitter(): print('记录美好生活') @auth2 def pipefish(): print('期待你的内涵神评论')
解决方式:
def auth(x): def auth2(func): def inner(*args, **kwargs): if login_status['status']: ret = func() return ret if x == 'wechat': username = input('请输入用户名:').strip() password = input('请输入密码:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret elif x == 'qq': username = input('请输入用户名:').strip() password = input('请输入密码:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret return inner return auth2 @auth('wechat') def jitter(): print('记录美好生活') @auth('qq') def pipefish(): print('期待你的内涵神评论')
@auth('wechat') :分两步:
第一步先执行auth('wechat')函数,获得返回值auth2
第二步@与auth2结合,造成装饰器@auth2 而后在依次执行。
这样就是带参数的装饰器,参数能够传入多个,通常带参数的装饰器在之后的工做中都是给你提供的, 你会用就行,可是本身也必定要会写,面试常常会遇到。