我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈
入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈
虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈
PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)文章合集 👈👈
Oracle数据库教程:👉👉 Oracle数据库文章合集 👈👈
优 质 资 源 下 载 :👉👉 资源下载合集 👈👈
Python基础进阶_装饰器
前言
- 装饰器(Decorator)是Python中一种特殊的语法, 是一种强大的函数或类修饰机制,用于在不修改原始函数或类代码的情况下,对其进行功能扩展或修改。
- 装饰器基于函数式编程的概念,通过将函数作为参数传递给另一个函数,并返回一个新的函数来实现
作用
- 装饰器是在不修改原始函数的函数名以及函数的前提下,给原始函数添加额外的功能或修改其行为
特点
- 装饰器的本质就是闭包,它需要把一个callable对象(能够打括号调用的对象)作为参数转递进来
- 装饰器是一个函数,它接受一个函数作为参数输入,并返回一个新的函数作为输出,这个新函数是内嵌原函数的函数
- python中的装饰器(decorator)一般采用语法糖的形式,是一种语法格式。比如:@classmethod,@staticmethod,@property,@wraps(),等都是python中的装饰器
“开放-封闭”原则
- 程序功能扩展时,不能违反软件开发中的一个原则:“开放-封闭”原则
- 开放-封闭 原则规定已经实现的功能代码不允许被修改,但可以被扩展,即:
- 封闭:已实现的功能代码块不允许被修改
- 开放:对现有功能的扩展开放
案例解析
-
基本需求:开发两个功能:发朋友圈和发消息。用户点击按钮1发朋友圈,用户点击按钮2发消息
# 功能代码,定义两个功能函数 def send_pyq(): print('用户点击了按钮1,执行发朋友圈动作....') def send_xx(): print('用户点击了按钮2,执行发消息动作....') # 业务逻辑代码 btn_index = input('用户按下了按钮(1, 2):') # 模拟用户按下按钮 # 根据用户按下按钮,分别执行不同功能代码 if btn_index == '1': send_pyq() elif btn_index == '2': send_xx()
- 上面的代码已经满足了基本需求,但是甲方爸爸发现这个好像所有人都可以操作,完全没管控…所以提出了新的需求!
-
需求升级:在用户进行发朋友圈和发消息之前对用户登录进行验证,已登录则可以操作,否则退出系统
- 解决思路1:在业务逻辑代码中添加判断,用户点击了按钮后,判断是否登录,登录了则执行功能函数,没登录则不执行…
# 功能代码,定义两个功能函数 def send_pyq(): print('用户点击了按钮1,执行发朋友圈动作....') def send_xx(): print('用户点击了按钮2,执行发消息动作....') # 业务逻辑代码 btn_index = input('用户按下了按钮(1, 2):') # 模拟用户按下按钮 # 根据用户按下按钮,分别执行不同功能代码 if btn_index == '1': login_status = input('获取用户登录状态:') # 模拟获取用户登录状态 if login_status == '已登录': send_pyq() else: exit() elif btn_index == '2': login_status = input('获取用户登录状态:') # 模拟获取用户登录状态 if login_status == '已登录': send_xx() else: exit()
这一段代码看上去很完美的解决了验证问题。but…这个里面有几个问题点:
- 1、 大家有没有想过,在一个系统程序中,可能有N多个界面可以进行发朋友圈和发消息的操作,难道我们要把所有界面的都撸一遍?把这个判断逻辑复制,全部粘贴一遍?
- 2、在实际开发的过程中,一个验证的过程可能有十几行甚至几十行代码,我们这样复制粘贴到各个地方,就会出现代码冗余并且不可重用的问题
-
解决方法
- 1、不可重用:定义一个函数,将整个验证过程封装起来
- 2、代码冗余:我们将这个验证函数写到功能函数内部
# 功能代码,定义两个功能函数 def send_pyq(): status = login_check() if status: print('用户点击了按钮1,执行发朋友圈动作....') else: exit() def send_xx(): status = login_check() if status: print('用户点击了按钮2,执行发消息动作....') else: exit() def login_check(): login_status = input('获取用户登录状态:') # 模拟获取用户登录状态 if login_status == '已登录': return True else: return False # 业务逻辑代码 btn_index = input('用户按下了按钮(1, 2):') # 模拟用户按下按钮 # 根据用户按下按钮,分别执行不同功能代码 if btn_index == '1': send_pyq() elif btn_index == '2': send_xx()
- 这样看上去好像解决了上面两个问题。但是这样写又出现了新问题…
- 1、这样写的代码违反了功能函数单一性原则(在发朋友圈和发消息的功能函数中,既做了登录验证,又做了发朋友圈/发消息的功能)
- 2、违反了软件开发中的“开放-封闭”原则,对已经实现的功能代码进行了修改
-
解决方法
- 为了解决上面的问题,可以考虑将登录验证单独封装成函数,然后将发朋友圈和发消息功能函数传入登录验证函数当中
# 功能代码,定义两个功能函数 def send_pyq(): print('用户点击了按钮1,执行发朋友圈动作....') def send_xx(): print('用户点击了按钮2,执行发消息动作....') def login_check(func): login_status = input('获取用户登录状态:') # 模拟获取用户登录状态 if login_status == '已登录': print('用户已登录.....') func() else: print('用户未登录,退出系统...') exit() # 业务逻辑代码 btn_index = input('用户按下了按钮(1, 2):') # 模拟用户按下按钮 # 根据用户按下按钮,分别执行不同功能代码 if btn_index == '1': login_check(send_pyq) elif btn_index == '2': login_check(send_xx)
- 上面的代码看上去已经解决了代码冗余的问题,但是又有了新的问题
- 1、这种写法需要去修改业务逻辑代码中的调用方法,如果用到该功能的界面比较多的话,那要修改的地方就很多,所以…
-
解决方法
- 在解决这个问题之前,我们需要了解一个知识点:
- 函数名的实质也就是一个变量名
- 变量也就可以被重新赋值
- 示例
def test1(): print('test1函数内部打印....') def test2(func): def inner(): print('test2函数内部打印....') func() return inner test1 = test2(test1) # 重新给test1赋值,此时test1 被赋值为 inner函数对象 test1 = inner test1() # test1() 就相当于 inner() # -----输出结果----- test2函数内部打印.... test1函数内部打印....
- 在这个示例代码中,我们用到了函数的闭包技巧
- 根据上面这个示例代码的思路,我们可以将我们的代码修改为
# 功能代码,定义两个功能函数 def send_pyq(): print('用户点击了按钮1,执行发朋友圈动作....') def send_xx(): print('用户点击了按钮2,执行发消息动作....') def login_check(func): def inner(): login_status = input('获取用户登录状态:') # 模拟获取用户登录状态 if login_status == '已登录': print('用户已登录.....') func() else: print('用户未登录,退出系统...') exit() return inner send_pyq = login_check(send_pyq) send_xx = login_check(send_xx) # 业务逻辑代码 btn_index = input('用户按下了按钮(1, 2):') # 模拟用户按下按钮 # 根据用户按下按钮,分别执行不同功能代码 if btn_index == '1': send_pyq() elif btn_index == '2': send_xx()
- 从上面代码可以看到,我们既避免了代码冗余,又做到了登录验证过程的重用性,而且不需要去修改业务逻辑代码里面的调用方法
- 在解决这个问题之前,我们需要了解一个知识点:
-
上面的代码,我们需要通过
send_pyq = login_check(send_pyq)
这种方式对函数进行重新赋值,写起来比较麻烦,而且不是那么好看 -
那么,python给我们提供了一种语法糖(使用 @ 语法符,在函数定义之前增加装饰器函数的名称)
-
语法
@decorator_func def my_func(): pass
-
我们使用语法糖的方式对我们的代码进行修改
# 功能代码,定义两个功能函数 def login_check(func): def inner(): login_status = input('获取用户登录状态:') # 模拟获取用户登录状态 if login_status == '已登录': print('用户已登录.....') func() else: print('用户未登录,退出系统...') exit() return inner @login_check def send_pyq(): print('用户点击了按钮1,执行发朋友圈动作....') @login_check def send_xx(): print('用户点击了按钮2,执行发消息动作....') # send_pyq = login_check(send_pyq) # send_xx = login_check(send_xx) # 业务逻辑代码 btn_index = input('用户按下了按钮(1, 2):') # 模拟用户按下按钮 # 根据用户按下按钮,分别执行不同功能代码 if btn_index == '1': send_pyq() elif btn_index == '2': send_xx()
装饰器的执行时间
- 当程序运行到
@装饰器函数名
这一句的时候,装饰器函数就会立即执行,并不是等到调用原函数的时候才执行def check(func): print('check装饰器函数') def inner(): print('验证操作....') func() return inner @check def send_pyq(): print('进行发朋友圈操作') # -----输出结果----- check装饰器函数
- 上面这段代码中,我们并没有调用原始函数
send_pyq()
,但是从输出结果可以看到装饰器函数check(func)
其实是已经执行了
装饰器执行图解
装饰器叠加
- 多个装饰器叠加:从上到下装饰,代码编译顺序:从下到上编译,调用执行顺序:从上到下输出
# 示例1 def decorator_outer(func): print('外层装饰器,decorator_outer') def outer_inner(): print('外层装饰器内部函数, outer_inner') func() return outer_inner def decorator_subcoat(func): print('内层装饰器,decorator_subcoat') def subcoat_inner(): print('内层装饰器内部函数, subcoat_inner') func() return subcoat_inner @decorator_outer @decorator_subcoat def send_pyq(): print('进行发朋友圈操作') print('==========分隔线==========') send_pyq() # -----输出结果----- 内层装饰器,decorator_subcoat 外层装饰器,decorator_outer ==========分隔线========== 外层装饰器内部函数, outer_inner 内层装饰器内部函数, subcoat_inner 进行发朋友圈操作 # ===================================== # 示例2 def decorator_outer(func): print('外层装饰器,decorator_outer') def outer_inner(): print('外层装饰器内部函数, outer_inner') func() return outer_inner def decorator_subcoat(func): print('内层装饰器,decorator_subcoat') def subcoat_inner(): print('内层装饰器内部函数, subcoat_inner') func() return subcoat_inner @decorator_subcoat @decorator_outer def send_pyq(): print('进行发朋友圈操作') print('==========分隔线==========') send_pyq() # -----输出结果----- 外层装饰器,decorator_outer 内层装饰器,decorator_subcoat ==========分隔线========== 内层装饰器内部函数, subcoat_inner 外层装饰器内部函数, outer_inner 进行发朋友圈操作
装饰器的应用
简单的装饰器
- 简单的装饰器,装饰函数只需要两层嵌套即可
def check(func): def inner(): print('验证操作....') func() return inner @check def send_pyq(): print('进行发朋友圈操作')
装饰器装饰带参数的函数
- 装饰带参数的函数,装饰函数嵌套的函数也需要带参数
- 通过
*args
和**kwargs
接收不定长参数def check(func): def inner(*args, **kwargs): print('登录验证操作...') func(*args, **kwargs) # 此时func指向 原send_pyq函数的内存地址,所以需要接收参数 return inner @check def send_pyq(content): print(f'进行发朋友圈操作:{content}') @check def send_xx(username, content): print(f'向{username}发送消息:{content}') send_pyq('朋友圈内容') # 通过装饰器之后,send_pyq指向inner函数的内存地址 send_xx('张三', '今天放假')
装饰器装饰有返回值的函数
- 装饰器的内嵌函数格式要保持与原函数的格式一致
def check(func): def inner(*args, **kwargs): print('登录验证操作...') res = func(*args, **kwargs) # res接收原函数执行的返回值 return res # 将res接收到的返回值,再作为inner函数的返回值 return inner @check def send_pyq(content): print(f'进行发朋友圈操作:{content}') return '发朋友圈成功' @check def send_xx(username, content): print(f'向{username}发送消息:{content}') re1 = send_pyq('文案内容') # 此时re1接收到的是inner函数的返回值,其实也就是原函数的返回值 re2 = send_xx('张三', '今天放假') print(re1) print(re2)
带参数的装饰器
- 通过
@装饰器(参数)
的方式,调用这个函数,并传递参数;并把返回值再次当做装饰器进行使用 - 先计算 @ 后面的内容,把这个内容的计算结果(返回值)当做一个装饰器
# 示例1:装饰器不带参数,要修改打印内容之前打印的分隔线很麻烦 def zsq(func): def inner(*args, **kwargs): print('-' * 30) func(*args, **kwargs) return inner @zsq def test(content): print(content) test('打印内容')
- 示例1中,我们装饰器已经写好了,但是现在客户需要在打印内容之前不是打印
-
了,想打印*
…那我们就要去修改装饰器。 - 但是,如果另一个用户又想在打印内容之前打印一行
=
怎么办呢? - 此时,我们就需要通过给装饰器添加参数来解决了…
def get_zsq(char): def zsq(func): def inner(*args, **kwargs): print(char * 30) func(*args, **kwargs) return inner return zsq @get_zsq('-') def test1(content): print(content) @get_zsq('*') def test2(content): print(content) test1('打印内容1') test2('打印内容2') # -----输出结果----- ------------------------------ 打印内容1 ****************************** 打印内容2
- 图解
def get_zsq(char): def zsq(func): def inner(*args, **kwargs): print(char * 30) func(*args, **kwargs) return inner return zsq @get_zsq('*') def test(content): print(content) test('打印内容')
个人理解
- 语法糖会将
@
符号后面当做一个整体,默认增加一个(原函数对象)
的参数 - 示例1: 不带参数装饰器
def get_zsq(char): print('char参数值:', char) def inner(*args, **kwargs): print(char * 30) func(*args, **kwargs) return inner @get_zsq # ---> 相当于 @get_zsq(test) def test(content): print(content) # -----执行结果---- char参数值: <function test at 0x000001E92B735310>
- 示例2: 带参数装饰器
def get_zsq(char): print('char参数值:', char) def zsq(func): def inner(*args, **kwargs): print(char * 30) func(*args, **kwargs) return inner return zsq @get_zsq('装饰器参数') # ---> 相当于 @get_zsq('装饰器参数')(test) def test(content): print(content) # -----执行结果---- char参数值: 装饰器参数