开放封闭
- 封闭:已经实现的功能代码块 不允许被修改
- 开放:但可以给这个代码块增加一些扩展
情景引入:
公司已经开发了一个登录的功能,每个人都可以登录。但由于疫情爆发,现在要对登录功能加个验证:只有不发骚的人才能登录。
装饰器写法
# ↓↓ 先来一个闭包def set_func(func):def call_func():print('……验证是否发骚中……')func()return call_func# @set_funcdef login():print('……排队进入中……')login() # 输出 ……排队进入中……
当取消第8行的注释时,输出
……验证是否发骚中…………排队进入中……
不改动函数内部的代码,利用@func的形式,给原函数增加功能,这个func就是装饰器
装饰器实现过程
def set_func(func):def call_func():print('……验证是否发骚中……')func()return call_funcdef login():print('……排队进入中……')# 步骤一:ret = set_func(login)ret()
用变量ret指向我的闭包函数,然后ret()执行,就完成了登录之前校验的需求。
变量名可以随意取,那么把ret叫做login也没问题:
# 步骤2——>因为ret可以随意起名login = set_func(login)login()
写法简化:
# 步骤3——>写法简化@set_func # 等价于 login = set_fun(login)def login():print('……排队进入中……')
对xx函数进行装饰
单个参数的函数
def set_func(func):def call_func(n):print('……验证是否发骚中……')func(n)return call_func@set_funcdef login(num):print('……%d号正在排队进入中……' % num)login(6)# 输出:# ……验证是否发骚中……# ……6号正在排队进入中……
不定长参数的函数
def set_func(func):def call_func(*args, **kwargs): # 这里的*表示告诉python这里用多个参数传递print('……验证是否发骚中……')func(*args, **kwargs) # 这里的*表示要进行拆包return call_func@set_funcdef login(num, *args, **kwargs):print('……%d号正在排队进入中……' % num)print('……正在排队进入中……', args)print('……正在排队进入中……', kwargs)login(6)login(6, 7)login(6, 7, 8)login(6, 7, 8, mm=10)
有返回值的函数
def set_func(func):def call_func(*args, **kwargs):print('……验证是否发骚中……')return func(*args, **kwargs)return call_func@set_funcdef login(num, *args, **kwargs):print('……%d号正在排队进入中……' % num)print('……正在排队进入中……', args)print('……正在排队进入中……', kwargs)return 'ok'ret = login(6)print(ret)
↑↑ 这也是通用装饰器的写法。
多个装饰器装饰同一函数
def set_func1(func):print("……开始装饰权限1……")def call_func(*args, **kwargs):print('……在原功能之前新增功能1……')func(*args, **kwargs)print('……在原功能之后新增功能1……')return call_funcdef set_func2(func):print("……开始装饰权限2……")def call_func(*args, **kwargs):print('……在原功能之前新增功能2……')func(*args, **kwargs)print('……在原功能之后新增功能2……')return call_func@set_func1@set_func2def login():print('……原函数执行中……')login()
终端结果:
……开始装饰权限2…………开始装饰权限1…………在原功能之前新增功能1…………在原功能之前新增功能2…………原函数执行中…………在原功能之后新增功能2…………在原功能之后新增功能1……
@set_func1@set_func2 # 我们可以把经过func2装饰后的整体看作一个函数,然后这个新函数被func1装饰def login():print('……原函数执行中……')
相当于:
@set_func1fun
这个fun相当于:
@set_func2def login():print('……原函数执行中……')
所以,装饰器是按照从内向外的顺序进行功能的增加的
类作为装饰器
class Test:def __init__(self, func):self.func = funcpassdef __call__(self, *args, **kwargs):print('……这里是装饰器添加的功能……')return self.func()@Test # 相当于 get_str = Test(get_str)def get_str():print('hahah')return 'haha'get_str()
可以按照@Test相当于get_str = Test(get_str)来倒推。
其他的,可以用@Test.xx来做扩展。
带有参数的装饰器
def set_func(func):def call_func():print('……权限级别1级,验证……')return func()return call_func@set_funcdef test1():print('……test1……')return 'ok'@set_func(1) # 带有参数的装饰器def test2():print('……test2……')return 'ok'
如果装饰器后带有参数(第15行),其实是做了两步操作
- 1、调用set_func且将1当作实参传递
- 2、用上一步调用得到的返回值,当作装饰器,再对test2进行装饰
# 修改原来的装饰器:在原装饰器的外部套函数def set_level(level_num):def set_func(func):def call_func():if level_num == 1:print('……权限级别1级,验证……')elif level_num == 2:print('……权限级别2级,验证……')return func()return call_funcreturn set_func@set_level(1)def test1():print('……test1……')return 'ok'
主要方法:
- 写好满足需求的普通装饰器(不带参数)
- 在原来的装饰器外层套函数,返回值是原装饰器
- 要用什么参数,就只在最外层的函数内传什么参数
