【Python】装饰器函数

专栏文章索引:Python

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

目录

1. 学习装饰器的基础

2.最简单的装饰器

3.闭包函数装饰器

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

5. 装饰器的好处

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

7. 装饰器传递参数

8. 结语


1. 学习装饰器的基础

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

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

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

2.最简单的装饰器

def hello(func):return funcdef greet():return "Hello World"greet = hello(greet)
print(greet()) # Hello World

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

def hello(func):return funcdef 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 timedef 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_resultreturn 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.mzph.cn/news/733150.shtml

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

相关文章

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

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

STL容器之哈希的补充——其他哈希问题

1.其他哈希问题 ​ 减少了空间的消耗&#xff1b; 1.1位图 ​ 位图判断在不在的时间复杂度是O(1)&#xff0c;速度特别快; ​ 使用哈希函数直接定址法&#xff0c;1对1映射&#xff1b; ​ 对于海量的数据判断在不在的问题&#xff0c;使用之前的一些结构已经无法满足&…

基于PSO粒子群算法的三角形采集堆轨道优化matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 假设一个收集轨道&#xff0c;上面有5个采集堆&#xff0c;这5个采集堆分别被看作一个4*20的矩阵&#xff08;下面只有4*10&#xff09;&#xff0c;每个模块&…

opencv编程

opencv编程 引言&#xff1a; ​ 本实验旨在介绍使用OpenCV进行图像处理和视频处理的基本操作。OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源计算机视觉库&#xff0c;提供了丰富的图像和视频处理功能&#xff0c;既可以进行图像的读取、显示…

187基于matlab的弹道目标跟踪滤波方法

基于matlab的弹道目标跟踪滤波方法&#xff0c;扩展卡尔曼滤波&#xff08;extended Kalman filter, EKF&#xff09;、转换测量卡尔曼滤波&#xff08;conversion measurement Kalman filter, CMKF&#xff09;跟踪滤波&#xff0c;得到距离、方位角、俯仰角误差结果。程序已调…

人工智能|机器学习——DBSCAN聚类算法(密度聚类)

1.算法简介 DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一种基于密度的聚类算法&#xff0c;簇集的划定完全由样本的聚集程度决定。聚集程度不足以构成簇落的那些样本视为噪声点&#xff0c;因此DBSCAN聚类的方式也可以用于异常点的检测。 2.算法原…

《汇编语言》- 读书笔记 - 第17章-实验17 编写包含多个功能子程序的中断例程

《汇编语言》- 读书笔记 - 第17章-实验17 编写包含多个功能子程序的中断例程 逻辑扇区根据逻辑扇区号算出物理编号中断例程&#xff1a;通过逻辑扇区号对软盘进行读写 代码安装 int 7ch 测试程序效果 实现通过逻辑扇区号对软盘进行读写 逻辑扇区 计算公式: 逻辑扇区号 (面号*8…

CSS基础知识

font-family: "Trebuchet MS", Verdana, sans-serif; 字体栈&#xff0c;浏览器会一个一个试过去看下哪个可以用 font-size16px; font-size1em; font-size100%;//相对于16px 字体大小&#xff0c;需要进行单位换算16px1em font-weightnormal;//400font-weight属性…

YoloV5改进策略:Block改进|自研Block,涨点超猛|代码详解|附结构图

涨点效果 参考模型 参考的Block,如下图: 我对Block做了修改,修改后的结构图如下: 代码详解 from timm.models.layers import DropPathfrom torch import Tensor def channel_shuffle(x: Tensor, groups:

用Java Socket手撸了一个HTTP服务器

作为一个 Java 后端&#xff0c;提供 HTTP 服务可以说是基本技能之一了&#xff0c;但是你真的了解 HTTP 协议么&#xff1f;你知道知道如何手撸一个 HTTP 服务器么&#xff1f;Tomcat 的底层是怎么支持 HTTP 服务的呢&#xff1f;大名鼎鼎的 Servlet 又是什么东西呢&#xff0…

可观测性是什么?新手入门指南!

如果您之前对可观测性重要性&#xff0c;益处&#xff0c;以及组成不甚了解&#xff0c;本文是一个合适的指南手册。 什么是可观测性&#xff1f; 可观测性被定义为根据系统产生的输出数据&#xff08;如日志&#xff0c;指标和链路追踪&#xff09;来衡量当前系统运行状态的…

代码理解 pseudo_labeled = outputs.max(1)[1]

import torchls torch.Tensor([[0.2,0.4],[0.3,0.2]]) print(ls.max(1))

STL之deque容器代码详解

1 基础概念 功能&#xff1a; 双端数组&#xff0c;可以对头端进行插入删除操作。 deque与vector区别&#xff1a; vector对于头部的插入删除效率低&#xff0c;数据量越大&#xff0c;效率越低。 deque相对而言&#xff0c;对头部的插入删除速度回比vector快。 vector访问…

Unity Shader实现UI流光效果

效果&#xff1a; shader Shader "UI/Unlit/Flowlight" {Properties{[PerRendererData] _MainTex("Sprite Texture", 2D) "white" {}_Color("Tint", Color) (1, 1, 1, 1)[MaterialToggle] PixelSnap("Pixel snap", float…

简洁的在线观影开源项目

公众号&#xff1a;【可乐前端】&#xff0c;每天3分钟学习一个优秀的开源项目&#xff0c;分享web面试与实战知识。 每天3分钟开源 hi&#xff0c;这里是每天3分钟开源&#xff0c;很高兴又跟大家见面了&#xff0c;今天介绍的开源项目简介如下&#xff1a; 仓库名&#xff1…

海外媒体发稿:7种媒体套餐推广策略解析-华煤舍

有效的媒体宣传策略对于产品或服务的推广至关重要。本文将介绍7种媒体套餐推广策略&#xff0c;帮助您惊艳市场&#xff0c;并取得成功。以下是每种策略的拆解描述&#xff1a; 1. 广告投放 广告投放是最常见的宣传手段之一。通过在各种媒体平台上购买广告&#xff0c;如电视、…

Web渗透测试流程

什么是渗透测试 渗透测试 (penetration test),是通过模拟恶意黑客的攻击方法&#xff0c;来评估计算机网络系统安全的一种评估方法。这个过程包括对系统的任何弱点、技术缺陷或漏洞的主动分析&#xff0c;这个分析是从一个攻击者可能存在的位置来进行的&#xff0c;并且从这个…

linux 查看打开使用了哪些端口

你可以使用 netstat 命令来查看Linux系统中正在使用的端口。例如&#xff0c;要查看所有正在使用的TCP和UDP端口&#xff0c;你可以运行&#xff1a; sudo netstat -tulpn如果你只想查看所有正在使用的TCP端口&#xff0c;你可以运行&#xff1a; sudo netstat -tpln 如果你只…

全网最最最详细centos7如何安装docker教程

在CentOS 7上安装Docker主要包括以下步骤&#xff1a; 1. 卸载旧版本的Docker 首先&#xff0c;需要确保系统上没有安装旧版本的Docker。可以通过以下命令来卸载它们&#xff1a; sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-late…

【Linux】编译器-gcc/g++使用

个人主页 &#xff1a; zxctscl 文章封面来自&#xff1a;艺术家–贤海林 如有转载请先通知 文章目录 1. 前言2. 初见gcc和g3. 程序的翻译过程3.1 预处理3.1.1 宏替换 去注释 头文件展开3.1.2 条件编译 3.2 编译3.3 汇编3.4 链接 4. 链接4.1 动态链接4.2 静态链接 1. 前言 在之…