当前位置: 首页 > news >正文

Python装饰器的使用详解

目录

1、函数装饰器

1.1、闭包函数

1.2、装饰器语法

1.3、装饰带参数的函数

1.4、被装饰函数的身份问题

1.4.1、解决被装饰函数的身份问题

1.5、装饰器本身携带/传参数

1.6、嵌套多个装饰器

2、类装饰器

装饰器顾名思义作为一个装饰的作用,本身不改变被装饰对象的原本功能,只是对被装饰对象扩展了额外的功能,装饰器分为两类:函数装饰器类装饰器

1、函数装饰器

说明:函数装饰器是一个函数接受另一个函数作为参数的函数,它包装了函数的行为,并返回包装后的函数。所以装饰器的目的是为了扩展函数的功能,而不是修改函数本身,装饰器器的本质就是闭包函数。下面说明一下什么是闭包函数:

1.1、闭包函数

闭包的定义:在函数嵌套的前提下,内部函数使用外部函数的变量,并且外部函数返回内部函数
我们把这个使用外部函数变量的内部函数称为闭包。

闭包函数的作用:变量在函数运行之后销毁,但有的时候需要这个函数的变量,这时候使用闭包函数解决。

闭包函数的三要素:

  • 1.实现函数嵌套
  • 2.内部函数使用外部函数的变量
  • 3.外部函数返回内部函数

我们来看一个示例:

def wrapper_hello(func: callable):def wrapper():print("this is start")func()print("this is end")return wrapperdef hello():print("hello world")# 闭包函数的使用
hello_new = wrapper_hello(hello)
# 函数的调用
hello_new()

运行结果:

解释:
上面的代码中定义了两个函数,其中hello()函数是一个普通函数,它的功能是打印hello world。
我们来看看wrapper_hello()函数具体实现了什么功能:

  • 1.首先它的参数func是一个可调用对象。
  • 2.然后它的内部定义了一个函数wrapper(),并把wrapper对象作为返回值。
  • 3.wrapper()函数内部执行过程:
    • 先打印输出了“this is start”
    • 然后执行func()
    • 最后打印输出"this is end"

所以我们可以说wrapper_hello()函数扩展了hello()函数的功能,hello()原本实现的功能并没有改变。

1.2、装饰器语法

说明:Python提供了一种语法来定义装饰器。称为糖语法(通过@修饰目标函数), 它可以将修饰后函数赋值给修饰函数本身,所以调用函数时,还是直接调用,装饰器只是给函数增加额外的功能,本身并不改变函数功能和调用执行方式。
示例:

def wrapper_hello(func: callable):def wrapper():print("this is start")func()print("this is end")return wrapper# 装饰器糖语法写法
@wrapper_hello
def hello():print("hello world")# 还是正常的调用函数
hello()# 调用等价于
# hello = wrapper_hello(hello)
# hello()

1.3、装饰带参数的函数

说明:因为Python不允许装饰器接受被装饰对象的参数,所以要想实现装饰带参数的函数,在装饰器内部函数中使用 *args 和 **kwargs 来实现。

示例:

def wrapper_hello(func: callable):def wrapper(*args, **kwargs):print("this is start")# 注意如果func函数有返回值,需要使用一个对象来接受返回值,不然func执行完成后就销毁了,也就得不到它原本的返回值。res = func(*args, **kwargs)print("fun执行结果:", res)print("this is end")return resreturn wrapper# 装饰器糖语法写法
@wrapper_hello
def hello(name):return "hello world %s"%name# 还是正常的调用函数
hello("Tom")

注意:要想获取目标函数的返回值结果,必须要在装饰器内部返回执行结果,否则无法获取执行的结果,原因是func函数执行完之后就会被销毁,所以需要在装饰器内部保存目标函数的执行结果。

1.4、被装饰函数的身份问题

说明:如果查看修饰后函数的名字,或者使用内置的help函数查看,发现被修饰函数的名字是wrapper。因为Python认为现在的函数是装饰器函数的内部函数。

示例:

def wrapper_hello(func: callable):def wrapper(*args, **kwargs):print("this is start")# 注意如果func函数有返回值,需要使用一个对象来接受返回值,不然func执行完成后就销毁了,也就得不到它原本的返回值。res = func(*args, **kwargs)print("fun执行结果:", res)print("this is end")return resreturn wrapper# 装饰器糖语法写法
@wrapper_hello
def hello(name):return "hello world %s"%name# 查看hello函数的名字
print(hello.__name__)
help(hello)

运行结果:

1.4.1、解决被装饰函数的身份问题

说明:可以使用functools.wraps装饰器解决这个问题,它的作用是保留原函数的信息。 这可以帮助我们在运行时获取原本对象的信息,比如函数的名字,参数等。

注:也可以显示使用wrapper.__name__ = func.__name__的方法实现

示例:

def wrapper_hello(func: callable):@functools.wraps(func)def wrapper(*args, **kwargs):print("this is start")# 注意如果func函数有返回值,需要使用一个对象来接受返回值,不然func执行完成后就销毁了,也就得不到它原本的返回值。res = func(*args, **kwargs)print("fun执行结果:", res)print("this is end")return res# 等价于@functools.wraps(func)的作用# wrapper.__name__ =  func.__name__return wrapper# 装饰器糖语法写法
@wrapper_hello
def hello(name):return "hello world %s"%name# 查看hello函数的名字
print(hello.__name__)
help(hello)

运行结果:

1.5、装饰器本身携带/传参数

说明:为了更好地理解装饰器参数的必要性, 我们实现一个repeat装饰器,它接受一个数字作为输入。这个装饰器的功能是:重复执行目标函数给定的次数。

示例1:

import functools
def repeat(num_times):def inner_repeat(func):@functools.wraps(func)def wrapper(*args, **kwargs):for i in range(num_times):result = func(*args, **kwargs)return resultreturn wrapperreturn inner_repeat@repeat(num_times=3)
def hello(name):print('hello {}'.format(name))hello('Tom')

示例2:

import functools
def repeat1(num_times):def repeat():def inner_repeat(func):@functools.wraps(func)def wrapper(*args, **kwargs):for i in range(num_times):result = func(*args, **kwargs)return resultreturn wrapperreturn inner_repeat# 注意这里返回的内部repeat函数的调用return repeat()@repeat1(num_times=3)
def hello(name):print('hello {}'.format(name))hello('Tom')

执行结果都为以下结果:

解释:
装饰器执行原理:不管装饰器嵌套了多少层函数,执行顺序是从最外层的函数开始执行也就是repeat1函数,原因是理解为队列,遵循先进先出的原理,所以从最外层的函数先执行。
所以若要装饰器可以传参数最多只需要嵌套3层即可,再嵌套就显得多余和没有必要。
注意:

  • 1、装饰器函数多层嵌套,需要每层都要有返回值,即每层返回对应的函数名。
  • 2、函数括号的参数属于整个函数内部的“全局变量”,也就是不管函数内部嵌套了多少层函数,都可以使用这些变量

1.6、嵌套多个装饰器

说明:通过堆叠的方式将多个装饰器应用到一个函数上。 这些装饰器按照顺序从上到下开始执行

示例:

import functools
# 装饰器嵌套
def start_end(func):@functools.wraps(func)def wrapper(*args, **kwargs):print('this is start')result = func(*args, **kwargs)print('this is end')return resultreturn wrapperdef debug(func):@functools.wraps(func)def wrapper(*args, **kwargs):args_repr = [repr(a) for a in args]kwargs_repr = [f"{k} = {v!r}" for k, v in kwargs.items()]signature = ", ".join(args_repr + kwargs_repr)# print(signature)print(f"calling {func.__name__} ({signature})")result = func(*args, **kwargs)print(f" {func.__name__!r} returned {result!r}")return resultreturn wrapperdef hello(func):@functools.wraps(func)def wrapper(*args, **kwargs):print("这里是hello函数开始")result = func(*args, **kwargs)print("这里是hello函数结束")return wrapper@start_end
@debug
@hello
def say_hello(name):res = f'hello {name}'print(res)return ressay_hello("张三")

运行结果:

解释:

从运行结果可以看出,多重装饰器的嵌套是从上至下开始执行的,但是并不是等一个装饰器执行完了再执行下一个,而是从第一个装饰器开始执行到目标函数停止继续寻找下一个装饰器执行,然后执行第二个装饰器同样执行到目标函数停止继续寻找下一个装饰器执行,按照该方式继续执行,直到执行到最后一个装饰器,才开始执行目标函数,然后层层返回到最外层。

执行原理:就是遵循的栈的先进后出原理。

注意:目标函数不管嵌套了多少层装饰器,目标函数有且仅执行一次。

2、类装饰器

说明:我们也可以使用类作为装饰器。 但是必须实现__call__()方法,目的是使我们的对象可调用。 类装饰器通常用于维护状态。

示例:我们记录函数被调用的次数。 __call__本质上和wrapper()方法是一样的。 它添加了一些功能,执行函数,并返回其结果。

注意:这里我们使用functools.update_wrapper()而不是functools.wraps()来保留我们的函数的信息。

示例:

import functoolsclass CountCallNums:def __init__(self, func):functools.update_wrapper(self,func)self.func = funcself.count = 0def __call__(self, *args, **kwargs):self.count += 1print(f"函数{self.func.__name__} 被执行了 {self.count}次")return self.func(*args, **kwargs)@CountCallNums
def hello():print("hello")hello()
hello()# 上面装饰器等价于
# hello = CountCallNums(hello)
# hello()
# hello()print(hello.__name__)
help(hello)

运行结果:

相关文章:

Python装饰器的使用详解

目录 1、函数装饰器 1.1、闭包函数 1.2、装饰器语法 1.3、装饰带参数的函数 1.4、被装饰函数的身份问题 1.4.1、解决被装饰函数的身份问题 1.5、装饰器本身携带/传参数 1.6、嵌套多个装饰器 2、类装饰器 装饰器顾名思义作为一个装饰的作用,本身不改变被装…...

基于springboot+vue的党员教育和管理系统

博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战,欢迎高校老师\讲师\同行交流合作 ​主要内容:毕业设计(Javaweb项目|小程序|Pyt…...

三个伪类让你的CSS代码更加优雅

公众号:程序员白特,欢迎一起交流学习~ 原文:CSS整洁之道——:is()、:where()和:has()的用法 - 掘金 (juejin.cn) 让我们写出优雅界面的CSS,它也总是把自己进化得更加优雅。 今天我们花5分钟时间学习三个优雅的CSS伪类&#xff1a…...

幻兽帕鲁联机服务器搭建

幻兽帕鲁联机服务器搭建 开通云服务器 云主机购买|香港云服务器|香港云主机|美国云服务器|弹性云主机租用尽在-特网科技 建议选择4核心 16G内存 10M带宽,可满足6-15人游玩 下载安装脚本 windows系统: 下载 http://downinfo.myhostadmin.net/palserver/install…...

京东商品优惠券API获取商品到手价

item_get_app-获得JD商品详情原数据 公共参数 请求地址: jd/item_get_app 名称类型必须描述keyString是调用key(必须以GET方式拼接在URL中)secretString是调用密钥api_nameString是API接口名称(包括在请求地址中)[item_search,i…...

Flutter Version Manager (FVM): Flutter的版本管理终极指南

Flutter笔记 Flutter Version Manager (FVM) - 文章信息 - Author: 李俊才 (jcLee95) Visit me at: https://jclee95.blog.csdn.netEmail: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csdn.net/qq_28550263/article/details/136300307 my-websit…...

Docker技术概论(3):Docker 中的基本概念

Docker技术概论(3) Docker 中的基本概念 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at: https://jclee95.blog.csdn.netMy WebSite:http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://…...

死记硬背spring bean 的生命周期

1.bean的生命周期 我们平常经常使用类似于new Object()的方式去创建对象,在这个对象没有任何引用的时候,会被gc给回收掉。而对于spring而言,它本身存在一个Ioc容器,就是用来管理对象的,而对象的生命周期也完全由这个容…...

海外网红营销策略:如何将红人粉丝有效转化为品牌忠实粉丝?

在数字化时代,社交媒体已经成为品牌推广和营销的不可或缺的一部分。随着海外网红营销的崛起,品牌们纷纷将目光投向这个新兴的营销领域。然而,仅仅依赖网红的影响力来达到品牌传播的效果是不够的,更为重要的是如何将网红的粉丝转化…...

java之Bean对象

1. 什么是Bean? Bean被实例化的,是被Spring框架所管理的Java对象。 Spring容器会自动完成Bean的实例化。将所创建的的Bean自动注入到Ioc容器中以供调用。 spring框架中 IOC容器中管理的对象就是Bean对象 2. 第三方bean Bean 因为第三方bean&#xff0…...

Flink——芒果TV的实时数仓建设实践

目录 一、芒果TV实时数仓建设历程 1.1 阶段一:Storm/Flink JavaSpark SQL 1.2 阶段二:Flink SQLSpark SQL 1.3 阶段三:Flink SQLStarRocks 二、自研Flink实时计算调度平台介绍 2.1 现有痛点 2.2 平台架构设计 三、Flink SQL实时数仓分…...

卸载云服务器上的 MySQL 数据库

执行以下命令以停止 MySQL 服务: sudo service mysql stop执行以下命令以彻底卸载 MySQL: sudo apt-get remove --purge mysql-server mysql-client mysql-common sudo apt-get autoremove sudo apt-get autoclean 这将删除 MySQL 数据库服务器、客…...

AUTOSAR SPI详解

1.SPI通信 1)SPI通信脚 SCLO:串行时钟sclk。MTSR:主向从方向数据MTSR(主发送从接收)。MRST:从向主方向数据MRST(主接收从发送)。SLSO:从选择信号SLS,支持16路片选控制。 2) SPI状态机 2.SPI通信波形 在主…...

SpringBoot快速入门(黑马学习笔记)

需求 需求:基于SpringBoot的方式开发一个Web应用,浏览器发起请求/hello后,给浏览器返回字符串"Hello World~"。 开发步骤 第一步:创建SpringBoot工程项目 第二步:定义HelloController类,添加方…...

压力测试工具Jmeter的下载与使用

1、进入官网下载Jmeter https://jmeter.apache.org/ 国内镜像(下载的慢的话可以用国内镜像下载) https://mirrors.cloud.tencent.com/apache/jmeter/binaries/ 2、跳转到下载页面 3、根据不同系统下载相应版本的Jmeter压缩包,Linux系统下载…...

kubectl 陈述式资源管理方法

目录 陈述式资源管理方法 项目的生命周期 1.创建kubectl create命令 2.发布kubectl expose命令 service的4的基本类型 查看pod网络状态详细信息和 Service暴露的端口 查看关联后端的节点 ​编辑 查看 service 的描述信息 ​编辑在 node01 节点上操作,查看…...

从 iOS 设备恢复数据的 20 个iOS 数据恢复工具

作为 iPhone、iPad 或 iPod 用户,您可能普遍担心自己可能会丢失存储在珍贵 iOS 设备中的所有宝贵数据。数据丢失的原因多种多样,这里列出了一些常见原因: 1. iOS 软件更新 2. 恢复出厂设置 3. 越狱 4. 误操作删除数据 5. iOS 设备崩溃 …...

cpp基础学习笔记03:类型转换

static_cast 静态转换 用于类层次结构中基类和派生类之间指针或者引用的转换。up-casting (把派生类的指针或引用转换成基类的指针或者引用表示)是安全的;down-casting(把基类指针或引用转换成子类的指针或者引用)是不安全的。用于基本数据类型之间的转换&#xff…...

H3C OSPF 外部路由引入实验

H3C OSPF 外部路由引入实验 实验拓扑 实验需求 按照图示配置 IP 地址R1,R2,R3 运行 OSPF 使内网互通,所有接口(公网接口除外)全部宣告进 Area 0;要求使用环回口作为 Router-id业务网段不允许出现协议报文…...

ARM简介

ARM:ARM是Advanced RISC Machine的缩写,意为高级精简指令集计算机。 英国ARM公司,2016年被软银创始人孙正义斥资320亿美元收购了。现在是软银旗下的芯片设计公司,总部位于英国剑桥,专注于设计芯片,卖芯片生…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...

接口测试中缓存处理策略

在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...

【Python】 -- 趣味代码 - 小恐龙游戏

文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

Cursor实现用excel数据填充word模版的方法

cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...

docker详细操作--未完待续

docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...

linux arm系统烧录

1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...

Nuxt.js 中的路由配置详解

Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

C++ 基础特性深度解析

目录 引言 一、命名空间(namespace) C 中的命名空间​ 与 C 语言的对比​ 二、缺省参数​ C 中的缺省参数​ 与 C 语言的对比​ 三、引用(reference)​ C 中的引用​ 与 C 语言的对比​ 四、inline(内联函数…...

ElasticSearch搜索引擎之倒排索引及其底层算法

文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...