Python 装饰器深入解析

2023-11-17

1. 什么是装饰器?

  • 装饰器是给现有的模块增添新的小功能,可以对原函数进行功能扩展,而且还不需要修改原函数的内容,也不需要修改原函数的调用。

1.1 装饰器的使用符合了面向对象编程的开放封闭原则。

开放封闭原则主要体现在两个方面:

  • 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
  • 对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类尽任何修改。

2. 引入

实现打印功能,发说说, 发图片, 但必须有一个前提, 就是,用户必须登录之后再进行说说图片的发送

# 定义两个功能函数
def fss():
    checkLogin()
    print("发说说")


def ftp():
    checkLogin()
    print("发图片")

# 登录验证的操作
def checkLogin():
    print("登录验证...")


# 相关的逻辑代码
btnIndex = 2
if btnIndex == 1:
    fss()
else:
    ftp()

执行结果:
在这里插入图片描述

  1. 直接在业务逻辑代码里面去修改, 添加一个验证操作
    因为业务逻辑代码非常多, 所以, 就造成了, 每一份, 逻辑代码, 在调用, 具体的功能函数之前, 都需要, 去做一个登录验证, 代码冗余度, 就比较大, 代码的复用性比较差, 代码的维护性比较差
  2. 直接在功能函数里面, 去修改, 方便代码的重用

3. 装饰器

@ :装饰器的语法糖

def checkLogin(func):
    def inner():
        print("登录验证...")
        func()
    return inner

# 加入装饰器后,自动执行了 fss = checkLogin(fss)
@checkLogin
def fss():
    print("发说说")

# 语法糖 写法,自动执行 ftp = checkLogin(ftp)
@checkLogin
def ftp():
    print("发图片")

# 相关的逻辑代码
btnIndex = 1
if btnIndex == 1:
    fss()
else:
    ftp()

执行结果:
在这里插入图片描述
可以看到,装饰器其实是一个闭包函数,当用 checkLogin 装饰 fss 时,会自动执行 fss = checkLogin(fss), 从而使得函数整体的功能发生了改变,封装到了闭包中,当再此调用 fss() 时,就会先输出登录验证…

在下述两种条件下,就需要使用装饰器来实现

  1. 函数名字不能发生改变
  2. 函数体内部的代码不能发生改变
def check(func):
    print("xxx")
    def inner():
        print("登录验证操作....")
        func()
    return inner

@check
def fss():
    print("发说说")

执行结果:
在这里插入图片描述
这里我们并没有显式的调用函数,却输出了 xxx,这也充分说明了,我们会先执行 fss = check(fss) 从而打印出了在外层的 xxx

4. 装饰器叠加

多个装饰器叠加时: 从内到外装饰、从外到内执行
从内到外装饰,即先利用 zhuangshiqi_star 装饰 print_content() 函数,再利用 zhuangshiqi_line 将之前看做一个整体进行装饰,在执行的时候,先执行最外层封装的,也就是 zhuangshiqi_line 然后再执行 zhuangshiqi_star 最后执行 print_content()

def zhuangshiqi_line(func):
    def inner():
        print("---------------------------")
        func()
    return inner


def zhuangshiqi_star(func):
    def inner():
        print("*" * 30)
        func()
    return inner

@zhuangshiqi_line # print_content = zhuangshiqi_line(print_content) 再装饰这个
@zhuangshiqi_star # print_content =  zhuangshiqi_star(print_content) 先装饰这个
def print_content():
    print("hello, world !")

print_content()

执行结果:
在这里插入图片描述

5. 对有参函数进行装饰器

在用 zsq 装饰 pnum 函数时,会首先自动执行 pnum = zsq(pnum),此时会将参数传递到 zsqinner 中,这里 inner 应该设置变长参数,*args, **kwargs,来接收参数,并在内部,执行 func(*args, **kwargs) 从而保持一致

def zsq(func):
    #  *args, **kawargs 用于接收不定长参数
    def inner(*args, **kwargs):
        print("_" * 30)
        # print(args, kwargs)
        func(*args, **kwargs) # 注意这里由于在本身函数调用时需要出传入参数执行
    return inner


@zsq # 装饰带多个参数的函数
def pnum(num, num2, num3):
    print(num, num2, num3)

@zsq  # 装饰带一个参数的函数
def pnum2(num):
    print(num)


pnum(123, 222, num3=666)
pnum2(999)

执行结果:
在这里插入图片描述

6. 带返回值的装饰器

在用 zsq 装饰 pnum 函数时,会首先自动执行 pnum = zsq(pnum),此时会将参数传递到 zsqinner 中,这里 inner 应该设置变长参数,*args, **kwargs,来接收,并且,由于需要返回值,在 inner 中执行 func 函数时,需要接收返回值,并再一次返回,也就是 return res 部分

def zsq(func):
    def inner(*args, **kwargs):
        print("_" * 30)
        # print(args, kwargs)
        res = func(*args, **kwargs)
        return res # 这里可以合并上下两行,直接写为 return func(*args, **kwargs)
    return inner

@zsq # pnum = zsq(pnum)
def pnum(num, num2, num3):
    print(num, num2, num3)
    return num + num2 + num3

@zsq
def pnum2(num):
    print(num)

res1 = pnum(123, 222, num3=666)
res2 = pnum2(999)

# 打印返回的参数
print(res1, res2)

执行结果:
在这里插入图片描述

7. 带参数的装饰器

先定义一个 getzsq 函数,函数的作用就是返回一个装饰器,返回的装饰器再接收一个 func 函数,装饰时自动执行 f1 = zsq(f1),内部包含一个闭包将其封装起来,这里闭包是固定格式,不能把参数加到闭包函数内,所有只能加在最外层,并返回一个装饰器,根据不同的参数,生成不同的装饰器返回

执行过程,先执行 getzsq(*), 返回的装饰器再执行 f1 = zsq(f1),进行装饰,此时的 char 值被封装到了 装饰器内部

def getzsq(char):
    def zsq(func):
        def inner():
            print(char * 30)
            func()

        return inner
    return zsq


@getzsq("*") # f1 = zsq(f1)
def f1():
    print("666")


f1()

执行结果:
在这里插入图片描述

上面例子中的装饰器是不是功能太简单了,那么装饰器可以加一些参数吗,当然是可以的,另外装饰的函数当然也是可以传参数的。

def logging(level):
    def outwrapper(func):
        def wrapper(*args, **kwargs):
            print("[{0}]: enter {1}()".format(level, func.__name__))
            return func(*args, **kwargs)
        return wrapper
    return outwrapper

@logging(level="INFO")
def hello(a, b, c):
    print(a, b, c)

hello("hello,","good","morning")

执行结果:
在这里插入图片描述

8. 类装饰器

类装饰器的实现是调用了类里面的__call__函数。类装饰器的写法比我们装饰器函数的写法更加简单。

类装饰器实现流程:

  • 通过__init__()方法初始化类
  • 通过__call__()方法调用真正的装饰方法
class logging(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("[DEBUG]: enter {}()".format(self.func.__name__))
        return self.func(*args, **kwargs)

@logging # 实际是执行 hello = logging(hello) 所以需要 __init__ 方法,并接收一个 func 参数
def hello(a, b, c):
    print(a, b, c)
# 经过 hello = logging(hello) 后返回的是一个实例方法,不能被直接调用,所以需要实现 __call__ 方法
hello("hello,","good","morning")

执行结果:
在这里插入图片描述

9. 带参数的类装饰器

如下实现:

class logging(object):
    def __init__(self, level):
        self.level = level

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print("[{0}]: enter {1}()".format(self.level, func.__name__))
            return func(*args, **kwargs)
        return wrapper

@logging(level="TEST")
def hello(a, b, c):
    print(a, b, c)

hello("hello,","good","morning")

执行结果:
在这里插入图片描述

Reference

1. 【Python】一文弄懂python装饰器(附源码例子)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python 装饰器深入解析 的相关文章

  • 为什么 Python 在导入脚本时只保存脚本的字节码?

    既然执行Python字节码会比运行原始源代码更快 因为Python不需要重新编译 为什么Python在导入脚本时只保存编译后的字节码呢 为每个执行的脚本保存 pyc 文件不是更好吗 无论如何 Python 解释器的启动时间都需要时间 即使您
  • 键入的完整命令行

    我想获得输入时的完整命令行 This join sys argv 在这里不起作用 删除双引号 另外 我不想重新加入已解析和拆分的内容 有任何想法吗 你太迟了 当键入的命令到达 Python 时 您的 shell 已经发挥了它的魔力 例如 引
  • 来自 pandas 数据帧的烛台图,用日期替换索引

    此代码给出了带有移动平均线的烛台图 但 x 轴位于索引中 我需要 x 轴位于日期中 需要做什么改变 import numpy as np import pandas as pd import matplotlib pyplot as plt
  • Matplotlib 图例,跨列添加项目而不是向下添加项目

    对于下面的简单绘图 有没有办法让 matplotlib 填充图例 以便它从左到右填充行 而不是第一列然后第二列 gt gt gt from pylab import gt gt gt x arange 2 pi 2 pi 0 1 gt gt
  • django 模板 - 如何动态访问变量?

    假设我有一个具有以下上下文的 django 模板 data1 this is data1 data2 this is data2 data name data2 现在我知道了data name 假设它是 data2 是否可以用它来访问变量d
  • 查找正在导入哪些 python 模块

    从应用程序中使用的特定包中查找所有 python 模块的简单方法是什么 sys modules是将模块名称映射到模块的字典 您可以检查其键以查看导入的模块 See http docs python org library sys html
  • 带有 mkdocs 的本地 mathjax

    我想在无法访问互联网的计算机上使用 MathJax 和 Mkdocs 因此我不能只调用 Mathjax CDN Config mkdocs yml site name My Docs extra javascript javascripts
  • 正在使用 PIL 保存损坏的图像

    我遇到一个问题 操作图像像素导致保存损坏的图像 因此 我使用 PIL 打开图像 然后将其转换为 NumPy 数组 image Image open myimage png np image np asarray image 然后 我转置图像
  • 类型错误:此 COM 对象无法自动执行 makepy 过程 - 请为此对象手动运行 makepy

    这是什么错误 回溯错误 C Users DELL PycharmProjects MyNew venv Scripts python exe C Users DELL PycharmProjects MyNew agaaaaain py T
  • 更改 Matplotlib 投影轴的背景颜色

    我正在尝试使用 Cartopy 创建一个图形 该图形需要在未投影的轴上绘制投影轴 这是一个尽可能简单的代码版本 它将轴上的内容替换为背景颜色 import matplotlib pyplot as plt import cartopy cr
  • 使用 if 语句的网格网格和用户定义函数的真值不明确

    假设我有一个函数f x y 足够光滑 然而 有些值仅在有限的意义上存在 以sin x x的价值x 0只存在于极限 x gt 0 中 在一般情况下 我用一个来处理这个问题if陈述 如果我在情节中使用它meshgrid我收到一条错误消息 Val
  • Python:在字典中查找具有唯一值的键?

    我收到一个字典作为输入 并且想要返回一个键列表 其中字典值在该字典的范围内是唯一的 我将用一个例子来澄清 假设我的输入是字典 a 构造如下 a dict a cat 1 a fish 1 a dog 2 lt unique a bat 3
  • 如何获取分类数据的分组条形图

    I have a big dataset with information about students And I have to build a graph of dependencies between different value
  • 从迭代器外部将 StopIteration 发送到 for 循环

    有几种方法可以打破一些嵌套循环 他们是 1 使用中断 继续 for x in xrange 10 for y in xrange 10 print x y if x y gt 50 break else continue only exec
  • Python 2.7 缩进错误[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 这个问题是由拼写错误或无法再重现的问题引起的 虽然类似的问题可能是on topic help on topic在这里 这个问题的解决方式不
  • UnicodeDecodeError:部署到 Heroku 时,“utf-8”编解码器无法解码位置 0 中的字节 0xff

    我尝试在heroku上部署我的简单django项目 但我不明白如何解决这个问题 这是git push heroku master remote Traceback most recent call last remote File tmp
  • 如何使用 enumerate 来倒数?

    letters a b c 假设这是我的清单 在哪里for i letter in enumerate letters 将会 0 a 1 b 2 c 我怎样才能让它向后枚举 如 2 a 1 b 0 c 这是一个很好的解决方案并且工作完美 i
  • Python 相当于 Scala 案例类

    Python 中是否有与 Scala 的 Case Class 等效的东西 就像自动生成分配给字段而无需编写样板的构造函数一样 当前执行此操作的现代方法 从 Python 3 7 开始 是使用数据类 https www python org
  • 使用 MPI 的 Allreduce 对 Python 对象求和

    我正在使用使用 Python 中的字典和计数器构建的稀疏张量数组操作 我想让并行使用这个数组操作成为可能 最重要的是 我最终在每个节点上都有计数器 我想使用 MPI Allreduce 或另一个不错的解决方案 将其添加在一起 例如 使用计数
  • 如何同时接受int和float类型的输入?

    我正在制作一个货币转换器 如何让 python 同时接受整数和浮点数 我就是这样做的 def aud brl amount From to ER 0 42108 if amount int if From strip aud and to

随机推荐

  • ES6入门

    一 let和const命令 1 let命令 类似于var 但是只在let所在的代码块有效 不存在变量提升 即一定要先声明后使用 暂时性死区 待理解 不允许重复声明 2 块级作用域 内层不影响外层 3 const命令 const声明一个常量
  • JAVA 什么是多态?

    面向对象编程有三大特性 封装 继承 多态 封装隐藏了类的内部实现机制 可以在不影响使用的情况下改变类的内部结构 同时也保护了数据 对外界而已它的内部细节是隐藏的 暴露给外界的只是它的访问方法 继承是为了重用父类代码 两个类若存在IS A的关
  • Ag Grid 组件 Vue Data Grid: Components

    目录 声明自定义组件 内联 组件 本地声明的组件 外部化的 JavaScript 组件 js 文件 外部化单文件组件 SFC vue 文件 注册自定义组件 注册内联自定义组件 注册非内联自定义组件 1 按名称 2 直接引用 已弃用 按名称引
  • 【yolov7系列二】正负样本分配策略

    本文主要就yolov7的正负样本筛选策略 并与yolov5 yolov6进行比对 首先接着上一篇yolov7系列一 网络整体结构 填几个小坑 希望对大家没有造成困扰 如 E ELAN层 在cat后需要要conv层做特征融合 还有SPPCSP
  • python virtualenv

    文章目录 powershell 参考文章 https www cnblogs com freely p 8022923 html https blog csdn net u012206617 article details 90294421
  • Inorder Successor in BST

    Given a binary search tree and a node in it find the in order successor of that node in the BST Note If the given node h
  • UNI-APP_APP(webview)集成X5内核

    官方文档 https uniapp dcloud net cn tutorial app android x5 html 腾讯TBS x5内核仅支持Android平台 iOS只能使用自带的WKWebview 打开项目的manifest js
  • Linux awk 命令

    AWK是一种处理文本文件的语言 是一个强大的文本分析工具 之所以叫AWK是因为其取了三位创始人 Alfred Aho Peter Weinberger 和 Brian Kernighan 的Family Name的首字符 语法 awk 选项
  • Keil环境下CANopenNode移植到STM32问题记录(一)---printf重定向问题

    文章目录 问题描述 问题结决 思考 相关文章 在直接将CANopenSTM32的示例工程直接移植到Keil环境下 如果移植工程未实现printf函数重定向 则要注释掉log printf下面的printf函数 使日志打印失效 Printf
  • SQL注入之盲注

    SQL注入之盲注 前言 一 盲注分类 二 具体解析 1 基于布尔的sql盲注 首先要先了解一下sql注入截取字符串常用的函数 1 mid 函数 2 substr 函数 3 left 函数 具体注入方法 2 基于时间的SQL盲注 3 基于报错
  • Java实验3与第五周总结

    1 已知字符串 this is a test of java 按要求执行以下操作 要求源代码 结果截图 统计该字符串中字母s出现的次数 统计该字符串中子串 is 出现的次数 统计该字符串中单词 is 出现的次数 实现该字符串的倒序输出 pu
  • BT5, depends* but it is not going to be installed 解决方法

    apt get install 任何包都缺少依赖项 执行以下命令 apt get f install 然后在 apt get install package package 你要安装的包 比如我安装的open vm tools 就成功了 后
  • VSCode代码自动补全(html标签、style样式、css属性及值)

    转自 传送门 1 按CTRL SHIFT P 2 输入搜索Suggest Snippets Prevent Quick Suggestions 控制在活动代码片段内是否禁用快速建议 3 取消选中 4 按CTRL SHIFT P输入搜索 Fi
  • 利用cygwin编译cholmod以获得在windows上可用的库lib

    原文http blog parlin me complie cholmod to get library for win64 记录要点 cygwin好好装 希望哪位神人能够提供一个好用的cygwin国内mirror 编译cholmod的时候
  • 同源策略与跨域

    前言 最近业务上前端同学多次联系说访问腾讯云cos资源的时候因为跨域的问题访问不到 大致看了下腾讯云关于设置跨域访问的教程 按照前端同学给的域名等选项就给配了 而且测试下来也是好的 但是呢一直不知道什么是跨域这里就做一个简单的学习记录 一
  • 批量给多台Android手机安装APK脚本

    问题场景 测试让开发给4台手机安装测试版的APK 现实跑4次程序 于是该程序说 要是有个一次性安装多台手机APK的方法就好了 于是该脚本就出现了 并且还可以安装多个apk 以上是2个apk同时给2台设备安装 一键即可 把需要安装的apk放到
  • 百度AI接口测试案列一:车牌识别

    1 打开百度AI网站 百度AI网站 2 登录百度账号 进入控制台 选择文字识别服务 如图 3 点击立即使用 然后创建应用 之后输入应用名称 描述 随便写 并选择应用类型 之后点击 立即创建 按钮 创建完毕 点击 返回应用列表 如下图 注 A
  • linux删除文件_Linux中如何删除常用方式无法删除的文件

    前言 我们都知道 在linux删除一个文件可以使用rm命令 但是有一些特殊名称的文件使用普通的rm方式却没法删除 本文介绍linux中删除特殊名称文件的多种方式 linux文件命名规则 在介绍之前 简单说明一下linux中文件命名规则 文件
  • RegExp正则表达式-基本语法

    RegExp 百度云资料 密码 f89c 里面有详细的语法跟例子 希望对大家有帮助 课前补充 转义字符 多行字符串 字符串换行符 n RegExp作用 匹配特殊字符或有特殊搭配原则的字符的最佳选择 创建方式 直接量 推荐 new RegEx
  • Python 装饰器深入解析

    1 什么是装饰器 装饰器是给现有的模块增添新的小功能 可以对原函数进行功能扩展 而且还不需要修改原函数的内容 也不需要修改原函数的调用 1 1 装饰器的使用符合了面向对象编程的开放封闭原则 开放封闭原则主要体现在两个方面 对扩展开放 意味着