【Python】装饰器函数

news2025/6/11 7:56:29

专栏文章索引:Python

原文章:装饰器函数基础_装饰函数-CSDN博客

目录

1. 学习装饰器的基础

2.最简单的装饰器

3.闭包函数装饰器

4.装饰器将传入的函数中的值大写

5. 装饰器的好处

6. 多个装饰器的执行顺序

7. 装饰器传递参数

8. 结语


1. 学习装饰器的基础

学习装饰器之前, 先需要明白函数的一些特性 :

  1. 函数也是对象, 可以将函数分配给变量, 也可以作为参数传递给另外一个函数,甚至可以储存在数据结构中。
  2. 函数内部也可以定义函数, 我们常常称这种函数为闭包函数, 其可以使用父函数的局部状态(变量)而非全局状态(变量)
    总之 ,python中一切皆对象, 不要把函数想的太高深。

概述:装饰器是可调用的, 将可调用的对象作为输入传入装饰器函数并返回另一个可调用对象。

2.最简单的装饰器

def hello(func):
    return func


def greet():
    return "Hello World"


greet = hello(greet)
print(greet()) # Hello World

我们定义了一个hello()函数, 它接受一个参数func, 其实在这里的func接受的是一个函数的内存地址, 这个函数就是后面定义的greet(), 它返回一句话, 将greet作为参数传给hello函数, 并接受返回值,这里需要注意的是, 返回值func在接受了greet()函数的内存地址后, 如果直接打印, 打印的会是一串内存地址

def hello(func):
    return func


def greet():
    return "Hello World"


greet = hello(greet)
print(greet) # <function greet at 0x02B27AE0>

因为在python中, 定义一个函数,相当于在内存空间开辟一块用于存放函数内容的空间 ,也就是我们案例中传给hello函数的值greet其实就是一块内存空间, 将它传给了func,这个时候内存空间的指向发生了变化, 不在执行greet函数而是赋值给了func。

不过这样做确实麻烦, 既然是装饰器, 装饰二字何来?,而且多层的函数调用嵌套容易发生错误, 所以python引用了@语法糖的形式, 使用@语法会立即修饰该函数,给它穿上一套准备好的衣服。修改一下代码:

def hello(func):
    return func

@hello
def greet():
    return "Hello World"

print(greet())

我们来理一下程序行走的流程, 经过hello函数, 不会立即执行, 预留一片储存空间, 存放返回的func, 接下来走到了@hello这里, 实际上就是greet = hello(greet),将greet作为参数传入hello函数, 并保留返回的结果func, 等待调用, 这个时候greet地址已经在hello函数中了,就等着调用了 ,所以最后执行调用, 程序进入func内存空间, 调用func地址中的内容, 也就是调用greet函数。

说到这里可能还是有点迷, 下面在举一个难点的例子, 用到了函数嵌套,闭包函数, 这也是常见的装饰器函数:

3.闭包函数装饰器

import time

def timer(func):
    def wapper(*args, **kwargs):
        print("开始计时")
        start_time = time.time()
        func()
        print('执行吗??')
        end_time = time.time()
        print('耗时: ', end_time - start_time)
    return wapper

@timer
def run():
    print('To do the job')
    time.sleep(2)
    print('End the job')
run()

咋一看挺复杂, 下面来理一理。
程序走到了@timer这里 ,实际上就是 run = timer(run), 在这里是将run作为参数传递给func, 相当于一个载体, 承载着run进入wrapper函数, 此时程序并不执行, 还没有最终的调用,只是返回一个wrapper地址,紧接着执行run函数,最后调用run(),注意:这时候直接执行run()函数, 也就是直接执行刚刚返回的wrapper内存地址,打印 开始计时, 这时候因为函数内部调用了func(),所以执行run函数,静止2秒, 打印最后一条数据, 其实就是func代替、等于run, 执行run函数里面的命令。

总结: 最终的函数调用时, 先进入装饰器函数, 顺次执行, 遇到传入的函数地址进行调用, 在依次执行
运行结果:

开始计时
To do the job
End the job
执行吗??
耗时:  2.0005037784576416

4.装饰器将传入的函数中的值大写

# 这是一个装饰器函数
def upperfunc(func):
    def wrapper():
        original_result = func() # 接受func()函数的返回值, 也就是要变为大写的值
        transform_result = original_result.upper()
        return transform_result
    return wrapper


@upperfunc
def greet():
    return "hello world"


print(greet())

# HELLO WORLD

同样的, 大体流程是 返回一个wrpper地址空间等待执行, 装饰器装饰greet函数,
使其中的返回值大写, 最后调用greet函数,首先执行装饰器函数中的地址空间, 在空间中将hello world大写, 并返回结果。

5. 装饰器的好处

说到这里该说一说装饰器的好处, 就像上面这个例子, 假设有一个你在程序中遇到一个问题, 需要将一些字符串大写, 而恰巧这些字符串数量不少, 一个一个调用upper方法是不是感觉头痛, 这时使用装饰器不啻为一个好的选择。


6. 多个装饰器的执行顺序

 如果我们想要定义多个装饰器且将这些装饰器装饰于一个函数, 那么装饰器的执行顺序是必须要清楚的,下面定义两个装饰器, 分别添加一些HTML标签, 由此观察装饰器的执行顺序

# div 标签包围
def div_tags(func):
    def wrapper():
        return '<div>' + func() + '</div>'
    return wrapper


#  a 标签包围
def a_tags(func):
    def wrapper():
        return '<a>' + func() + '</a>'
    return wrapper


@a_tags
@div_tags
def greet():
    return "hello world"


print(greet())

结果:

<a><div>hello world</div></a>

从结果可以看出多个装饰器是从下向上执行的,
回到上面说的装饰器的好处, 我们来看一下不用装饰器执行是怎么调用的:

decorated_greet = div_tas(a_tags(greet))
print(decorated_greet())

执行结果是一样的, 这样写不仅容易发生错误, 而且多重的嵌套很难让人提起兴趣去阅读代码是怎样执行的。


7. 装饰器传递参数

 上面讲的都是一些简单无参的函数, 下面来介绍一下有参数传递的装饰器函数 ,在这里就需要用到 * 操作符了, 它会接受一个或多个位置参数, 而** 操作符接受关键字参数
如果这里不懂的可以百度一下, 应该都有

# 装饰器传递参数
def Prinname(func):
    def wapper(*args, **kwargs):
        func(**kwargs)
    return wapper

@Prinname
def run(name):
    print('姓名:', name)


def main():
    run(name='bai')


if __name__ == '__main__':
    main()

在这里闭包函数中有两个形参, 他们基本上可以接受任何传入的参数, 所有下面run函数传入了关键字参数,类似于key1=value1, key2=value2, 只不过定义了个函数入口, 从这里开始执行,大体原理没变。

结果:

姓名: bai


8. 结语

以上是装饰器函数的一些基本内容, 要尝试着敲一遍才能理解, 如果第一遍看不懂的话,不要慌, 因为装饰器,闭包函数,*操作符等算是python中的中高级内容了, 我也是学习了好多遍,虽然实际中从没用过,但是学学对于理解pyhton的特性还是很有帮助的, 最后, 希望这篇文章可以帮到你!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1504221.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【UE5】创建蓝图

创建GamePlay需要的相关蓝图 项目资源文末百度网盘自取 在 内容游览器 文件夹中创建文件夹&#xff0c;命名为 Blueprints &#xff0c;用来放这个项目的所有蓝图(Blueprint) 在 Blueprints 文件夹下新建文件夹 GamePlay ,用存放GamePlay相关蓝图 在 Blueprints 文件夹下创建文…

Java17 --- SpringCloud初始项目创建

目录 一、cloud项目创建 1.1、项目编码规范 1.2、注解生效激活 1.3、导入父工程maven的pom依赖 二、创建子工程并导入相关pom依赖 2.1、相关配置文件 2.1.1、数据库配置文件内容 2.1.2、自动生成文件配置内容 三、创建微服务8001子工程 3.1、导入相关pom依赖 3.…

利用IDEA创建Java项目使用Servlet工具

【文件】-【项目结构】 【模块】-【依赖】-【】-【JAR】 找到Tomcat的安装路径打开【lib】找到【servlet.jar】点击【确定】 勾选上jar,然后【应用】-【确定】 此时新建文件可以发现多了一个Servlet&#xff0c;我们点击会自动创建一个继承好的Servlet类

对比学习概念与如何标注标签

对比学习公式讲述 对比学习倾向于将同一图像的转换视图之间的一致性最大化&#xff0c;而将不同图像的转换视图之间的一致性最小化。令是一个输出特征空间的卷积神经网络。一个图像x的两个增广图像补丁通过进行映射&#xff0c;生成一个查询特征q和一个关键特征k。此外&#x…

ospf静态路由实验简述

1、ospf静态路由实验简述 实验拓扑图 实验命令 r2: sys sysname r2 undo info enable int loopb 0 ip add 2.2.2.2 32 quit int e0/0/0 ip add 23.1.1.2 24 quit ospf 1 area 0 network 23.1.1.0 0.0.0.255 network 2.2.2.2 0.0.0.0 ret r3: sys sysname r3 undo info enable …

2024pytest自动化测试框架学习(二)

在自动化测试中&#xff0c;参数化非常常见。当你在测试某一个接口时&#xff0c;通常会给测试函数传递很多参数&#xff0c;达到遍历的目的。比如测试登录接口&#xff0c;我们需要模拟各种场景的账号密码。又如我们上一篇文章中介绍的获取天气接口&#xff0c;你需要验证很多…

面向切面编程(AOP)介绍(横切关注点、通知(增强)、连接切入点、切面)

1. 面向切面编程思想AOP AOP&#xff1a;Aspect Oriented Programming面向切面编程 AOP可以说是OOP&#xff08;Object Oriented Programming&#xff0c;面向对象编程&#xff09;的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构&#xff0c;用于模拟公…

docker-swarm集群搭建

目录 一、docker swarm介绍 二、部署docker 三、搭建集群 3.1 工作模式 3.2 将当前主机作为leader 3.3 将第二个节点slave1加入到worker 3.4 将第三个节点slave2也加入到worker 3.5 将第四个节点(slave3)加入到manager 四、总结 一、docker swarm介绍 Docker Swarm…

解锁安卓开发利器:深度探析ADB【安卓开发】

引言 在安卓开发与维护过程中&#xff0c;我们经常会遇到一些限制&#xff0c;比如无法直接访问某些系统功能&#xff0c;或者在某些定制系统中 受到限制 。为了解决这些问题&#xff0c;我们需要一种有效的工具来管理和调试安卓设备&#xff0c;而这时候ADB&#xff08;Andro…

如何变得心智成熟?我推荐你读这5本书

一个人若总是在底层混&#xff0c;说明他的脑子确实不怎么样&#xff0c;一群底层的人聚在一起就更完蛋。 变化是常态&#xff0c;成长是选择。无法否定过去相信的东西&#xff0c;是你最大的障碍。 今天&#xff0c;为大家推荐一份“心智书单”。 01 《打开心智》 李睿秋提…

《汇编语言》- 读书笔记 - 第16章-直接定址表

《汇编语言》- 读书笔记 - 第16章-直接定址表 16.1 描述了单元长度的标号&#xff08;数据标号&#xff09;检测点 16.1 16.2 在其他段中使用数据标号assume通过标号取地址检测点 16.2 16.3 直接定址表&#xff08;Direct Addressing Table&#xff09;例1分析代码效果 例2分析…

HPE ProLiant MicroServer Gen8驱动程序下载(windows)

记录下&#xff0c;以方便需要重装系统时将驱动更新到最后版本。 共有下面设备有适用的驱动可用&#xff1a; 1、系统管理&#xff1a; iLO 4 Channel Interface Driver for Windows Server 2016 下面这个驱动&#xff0c;安装后不知道有什么用 iLO 3/4 Management Control…

人力资源社会保障部教育部关于印发《关于深化中小学教师职称制度改革的指导意见》的通知

人力资源社会保障部、教育部印发 关于《深化中小学教师职称制度改革的指导意见》的通知 人社部发[2015]79号 各省、自治区、直辖市及新疆生产建设兵团人力资源社会保障厅&#xff08;局&#xff09;、教育部门&#xff08;教委、教育局&#xff09;&#xff1a; 为深化教育…

java——枚举,lambda

文章目录 枚举的使用使用场景switch语句常用方法 lambdalambda的前置知识什么是函数式接口 lambda的基本语法lambda注意事项 枚举的使用 枚举是在JDK1.5以后引入的。主要用途是&#xff1a;将一组常量组织起来&#xff0c;在这之前表示一组常量通常使用定义常量的方 式 publi…

OpenCV学习笔记(四)——对视频的读取操作

目录 读取视频内容 将彩色视频转换为灰色视频 读取视频内容 读取视频文件通常分为读取文件、验证是否打开成功打开文件、逐帧读取视频文件、释放资源和关闭窗口 &#xff08;1&#xff09;读取文件 在OpenCV中&#xff0c;通常使用VedioCapture来读取视频流&#xff0c;Vedi…

Vue.js数据绑定解密:深入探究v-model和v-bind的原理与应用

hello宝子们...我们是艾斯视觉擅长ui设计和前端开发10年经验&#xff01;希望我的分享能帮助到您&#xff01;如需帮助可以评论关注私信我们一起探讨&#xff01;致敬感谢感恩&#xff01; Vue.js数据绑定解密&#xff1a;深入探究v-model和v-bind的原理与应用 一、引言 Vue.…

OpenAI GPT LLMs 高级提示词工程方法汇总

原文地址&#xff1a;An Introduction to Prompt Engineering for OpenAI GPT LLMs Github&#xff1a;Prompt-Engineering-Intro 2023 年 3 月 2 日 Naive 提示词&#xff1a;带有提示的情感分类器 prompt Decide whether a Tweets sentiment is positive, neutral, or …

HashSet在添加元素时,是如何判断元素重复的?

前言&#xff1a;我们知道Set中所存储的元素是不重复的&#xff0c;那么Set接口的实现类HashSet在添加元素时是怎么避免重复的呢&#xff1f; HashSet在添加元素时&#xff0c;是如何判断元素重复的? ● 在底层会先调用hashCode()&#xff0c;注意&#xff0c;Obje…

anaconda问题合集

目录 一. 万分注意 二. ImportError: DLL load failed while importing _ctypes: 找不到指定的模块。 1. 发生情况 2. 导致结果和解决方法 三. WARNING: A newer version of conda exists. 1. 在conda install 某库的时候 2. 解决方法 一. 万分注意 不要轻易使用 conda …

英文版大宗现货商品挂牌交收系统

我们倾力打造了一款英文版大宗现货商品挂牌交收系统&#xff0c;旨在为全球贸易提供更为高效、便捷、安全的解决方案。 一、系统概述 英文版大宗现货商品挂牌交收系统是一款集商品信息发布、交易撮合、交收管理于一体的综合性平台。通过先进的互联网技术&#xff0c;我们实现…