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)

运行结果:

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

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

相关文章

Acwing 周赛135 解题报告 | 珂学家 | 反悔堆贪心

前言 整体评价 VP了这场比赛, T3挺有意思的,反悔贪心其实蛮套路的。 A. 买苹果 思路: 签到 n, x list(map(int, input().split())) print (n // x)B. 牛群 思路: 分类讨论 from collections import Counters input() cnt Counter(s)lists sorte…

WPF 【十月的寒流】学习笔记(2):MVVM中是怎么实现通知的

文章目录 前言相关链接代码仓库项目配置代码初始代码ViewPersonViewModel 尝试老办法通知解决方案ObservableCollectionBindingListICollectionView 总结 前言 我们这次详细了解一下列表通知的底层是怎么实现的 相关链接 十月的寒流 MVVM实战技巧之:可被观测的集合…

2024年【A特种设备相关管理(电梯)】考试总结及A特种设备相关管理(电梯)证考试

题库来源:安全生产模拟考试一点通公众号小程序 2024年A特种设备相关管理(电梯)考试总结为正在备考A特种设备相关管理(电梯)操作证的学员准备的理论考试专题,每个月更新的A特种设备相关管理(电梯…

KVM部署Windriver Linux操作系统

安装前准备 创建密码配置文件&#xff0c;否则虚机启动后无法登录 cd /var/lib/libvirt/images/disks/windriver/ docker run -ti --rm quay.io/coreos/mkpasswd --methodyescrypt 123456 >password_hash.txt cat <<-ENDOF> sample.bu variant: fcos version: 1.4…

面试 Java 基础八股文十问十答第十二期

面试 Java 基础八股文十问十答第十二期 作者&#xff1a;程序员小白条&#xff0c;个人博客 相信看了本文后&#xff0c;对你的面试是有一定帮助的&#xff01;关注专栏后就能收到持续更新&#xff01; ⭐点赞⭐收藏⭐不迷路&#xff01;⭐ 1&#xff09;创建一个对象用什么关…

代码随想录day27:贪心part1,基础篇

文章目录 day27&#xff1a;贪心part1&#xff0c;基础篇455.分发饼干376.摆动序列53.最大子数组和 day27&#xff1a;贪心part1&#xff0c;基础篇 455.分发饼干 循环结束条件注意饼干比孩子多的情况 class Solution {public int findContentChildren(int[] g, int[] s) {A…

C++:非静态成员默认初始化

C11之前只有常静态成员变量才能进行默认初始化&#xff0c;其它变量初始化时总要进行繁琐的过程 class A{int a; public:A():a(10){} };C11开始支持非静态成员的默认初始化&#xff0c;默认初始化和初始化参数列表同时初始化一个变量时会使用初始化参数列表&#xff0c;不进行…

JavaScript new、apply call 方法

new、apply、call、bind JavaScript 中的 apply、call和 bind 方法是前端代码开发中相当重要的概念&#xff0c;并且与 this 的指向密切相关 new new 关键词的主要作用 就是执行一个构造函数、返回一个实例对象 根据构造函数的情况&#xff0c;来确定是否可以接受参数的传递…

Huggingface初上手即ERNIE-gram句子相似性实战

大模型如火如荼的今天&#xff0c;不学点语言模型&#xff08;LM&#xff09;相关的技术实在是说不过去了。只不过由于过往项目用到LM较少&#xff0c;所以学习也主要停留在直面——动眼不动手的水平。Huggingface&#xff08;HF&#xff09;也是现在搞LM离不开的工具了。 出于…

最新 DataGrip 2023.3.4 下载安装激活 + 永久免费

文章目录 DataGrip简介同类产品对比使用技巧不足实战 下载安装激活Stage 1 : 官网下载Stage 2 : 下载工具Stage 3-1 : windows为例Stage 3-2 : mac为例常见问题部分小伙伴 Mac 系统执行脚本遇到如下错误&#xff1a;解决方法&#xff1a; 执行脚本做了啥&#xff1f;和收费版区…

基于springboot+vue的可盈保险合同管理系统

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

XUbuntu22.04之如何找到.so库所在的软件包?(二百一十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

Vue中的事件总线(EventBus)是什么?它有什么优点和缺点?

作为一名使用Vue的前端开发者&#xff0c;有时候会听到事件总线(EventBus)这个名词。但可能是我入行比较晚&#xff0c;我在Vue网站中并没有看到过事件总线的介绍&#xff0c;在项目中也没有使用过。那究竟什么是事件总线&#xff1f;事件总线可以解决什么问题&#xff1f; 事…

element-plus表格合并

要实现这样的表格&#xff0c; 怎么做呢&#xff1f; 甚至是这种三级的呢&#xff1f; 官网的案例也是通过这个方法进行配置的&#xff0c;也就是说表格长什么样&#xff0c;关键在怎么处理的方法上。 这是官网的方法&#xff0c;可参考拓展&#xff1a; const arraySpanMeth…

一款云渗透工具 - Sea Moon

SeaMoon - 月海 什么是月海 &#x1f315; 月出于云却隐于海 月海(Sea Moon) 是一款 FaaS/BaaS 实现的 Serverless 网络工具集&#xff0c;期望利用云原生的优势&#xff0c;实现更简单、更便宜的网络功能。 月海之名取自于苏轼的《西江月顷在黄州》&#xff0c;寓意月海取自…

【JVM】JVM相关机制

1. JVM内存区域划分 1.1 内存区域划分简介 内存区域划分&#xff1a;实际上JVM也是一个进程&#xff0c;进程运行时需要向操作系统申请一些系统资源&#xff08;内存就是典型的资源&#xff09;&#xff0c;这些内存空间就支撑着后续Java程序的运行&#xff0c;而这些内存又会…

Python环境搭建:一站式指南

在当前AIGC技术蓬勃发展的背景下&#xff0c;Python作为人工智能领域最受青睐的编程语言之一&#xff0c;成为我们必须掌握的技能。因此&#xff0c;搭建一个适合自己的Python环境成为了每个Python开发者的首要任务。本文将为您提供一站式的Python环境搭建指南&#xff0c;帮助…

PythonOpenCV随机粘贴图像

import cv2 import numpy as np import random # 读取两个图像 image1 cv2.imread(image1.jpg) image2 cv2.imread(image2.jpg) # 将image1转换为灰度图像 gray_image1 cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY) # 创建掩码&#xff0c;黑色部分为0&#xff0c;非黑色部…

Python自动发邮件

我经常需要用手机看服务器的运行情况&#xff0c;所以就写一个脚本&#xff0c;通过邮件把服务器运行情况发送给我&#xff0c;直接手机可以查看炼丹状态。事实证明还是很有用的&#xff0c;所以撰写一篇博文将脚本分享给大家。这里用到smtplib和email两个python包。 import s…

力扣精选算法100道——颜色分类(双指针和三指针俩种方法解决此题)

目录 &#x1f6a9;了解题意 &#x1f6a9;算法分析 第一种方法&#xff1a;双指针 &#x1f6a9;代码实现一 第二种方法&#xff1a;三指针 &#x1f6a9;代码实现二 &#x1f6a9;了解题意 本题将整数0&#xff0c;1&#xff0c;2代表红白篮&#xff0c;nums中的整数并…