开放封闭

  • 封闭:已经实现的功能代码块 不允许被修改
  • 开放:但可以给这个代码块增加一些扩展

情景引入:

公司已经开发了一个登录的功能,每个人都可以登录。但由于疫情爆发,现在要对登录功能加个验证:只有不发骚的人才能登录。

装饰器写法

  1. # ↓↓ 先来一个闭包
  2. def set_func(func):
  3. def call_func():
  4. print('……验证是否发骚中……')
  5. func()
  6. return call_func
  7. # @set_func
  8. def login():
  9. print('……排队进入中……')
  10. login() # 输出 ……排队进入中……

当取消第8行的注释时,输出

  1. ……验证是否发骚中……
  2. ……排队进入中……

不改动函数内部的代码,利用@func的形式,给原函数增加功能,这个func就是装饰器

装饰器实现过程

  1. def set_func(func):
  2. def call_func():
  3. print('……验证是否发骚中……')
  4. func()
  5. return call_func
  6. def login():
  7. print('……排队进入中……')
  8. # 步骤一:
  9. ret = set_func(login)
  10. ret()

用变量ret指向我的闭包函数,然后ret()执行,就完成了登录之前校验的需求。

变量名可以随意取,那么把ret叫做login也没问题:

  1. # 步骤2——>因为ret可以随意起名
  2. login = set_func(login)
  3. login()

写法简化:

  1. # 步骤3——>写法简化
  2. @set_func # 等价于 login = set_fun(login)
  3. def login():
  4. print('……排队进入中……')

对xx函数进行装饰

单个参数的函数

  1. def set_func(func):
  2. def call_func(n):
  3. print('……验证是否发骚中……')
  4. func(n)
  5. return call_func
  6. @set_func
  7. def login(num):
  8. print('……%d号正在排队进入中……' % num)
  9. login(6)
  10. # 输出:
  11. # ……验证是否发骚中……
  12. # ……6号正在排队进入中……

不定长参数的函数

  1. def set_func(func):
  2. def call_func(*args, **kwargs): # 这里的*表示告诉python这里用多个参数传递
  3. print('……验证是否发骚中……')
  4. func(*args, **kwargs) # 这里的*表示要进行拆包
  5. return call_func
  6. @set_func
  7. def login(num, *args, **kwargs):
  8. print('……%d号正在排队进入中……' % num)
  9. print('……正在排队进入中……', args)
  10. print('……正在排队进入中……', kwargs)
  11. login(6)
  12. login(6, 7)
  13. login(6, 7, 8)
  14. login(6, 7, 8, mm=10)

有返回值的函数

  1. def set_func(func):
  2. def call_func(*args, **kwargs):
  3. print('……验证是否发骚中……')
  4. return func(*args, **kwargs)
  5. return call_func
  6. @set_func
  7. def login(num, *args, **kwargs):
  8. print('……%d号正在排队进入中……' % num)
  9. print('……正在排队进入中……', args)
  10. print('……正在排队进入中……', kwargs)
  11. return 'ok'
  12. ret = login(6)
  13. print(ret)

↑↑ 这也是通用装饰器的写法。

多个装饰器装饰同一函数

  1. def set_func1(func):
  2. print("……开始装饰权限1……")
  3. def call_func(*args, **kwargs):
  4. print('……在原功能之前新增功能1……')
  5. func(*args, **kwargs)
  6. print('……在原功能之后新增功能1……')
  7. return call_func
  8. def set_func2(func):
  9. print("……开始装饰权限2……")
  10. def call_func(*args, **kwargs):
  11. print('……在原功能之前新增功能2……')
  12. func(*args, **kwargs)
  13. print('……在原功能之后新增功能2……')
  14. return call_func
  15. @set_func1
  16. @set_func2
  17. def login():
  18. print('……原函数执行中……')
  19. login()

终端结果:

  1. ……开始装饰权限2……
  2. ……开始装饰权限1……
  3. ……在原功能之前新增功能1……
  4. ……在原功能之前新增功能2……
  5. ……原函数执行中……
  6. ……在原功能之后新增功能2……
  7. ……在原功能之后新增功能1……
  1. @set_func1
  2. @set_func2 # 我们可以把经过func2装饰后的整体看作一个函数,然后这个新函数被func1装饰
  3. def login():
  4. print('……原函数执行中……')

相当于:

  1. @set_func1
  2. fun

这个fun相当于:

  1. @set_func2
  2. def login():
  3. print('……原函数执行中……')

所以,装饰器是按照从内向外的顺序进行功能的增加的

类作为装饰器

  1. class Test:
  2. def __init__(self, func):
  3. self.func = func
  4. pass
  5. def __call__(self, *args, **kwargs):
  6. print('……这里是装饰器添加的功能……')
  7. return self.func()
  8. @Test # 相当于 get_str = Test(get_str)
  9. def get_str():
  10. print('hahah')
  11. return 'haha'
  12. get_str()

可以按照@Test相当于get_str = Test(get_str)来倒推。

其他的,可以用@Test.xx来做扩展。

带有参数的装饰器

  1. def set_func(func):
  2. def call_func():
  3. print('……权限级别1级,验证……')
  4. return func()
  5. return call_func
  6. @set_func
  7. def test1():
  8. print('……test1……')
  9. return 'ok'
  10. @set_func(1) # 带有参数的装饰器
  11. def test2():
  12. print('……test2……')
  13. return 'ok'

如果装饰器后带有参数(第15行),其实是做了两步操作

  • 1、调用set_func且将1当作实参传递
  • 2、用上一步调用得到的返回值,当作装饰器,再对test2进行装饰
  1. # 修改原来的装饰器:在原装饰器的外部套函数
  2. def set_level(level_num):
  3. def set_func(func):
  4. def call_func():
  5. if level_num == 1:
  6. print('……权限级别1级,验证……')
  7. elif level_num == 2:
  8. print('……权限级别2级,验证……')
  9. return func()
  10. return call_func
  11. return set_func
  12. @set_level(1)
  13. def test1():
  14. print('……test1……')
  15. return 'ok'

主要方法:

  • 写好满足需求的普通装饰器(不带参数)
  • 在原来的装饰器外层套函数,返回值是原装饰器
  • 要用什么参数,就只在最外层的函数内传什么参数