Python 装饰器

news2024/11/20 12:44:36

一、什么是装饰器

装饰器本质上就是一个Python函数,它可以装饰在其他函数上,使得其他函数不需要做任何改动就可以获得装饰器函数中的功能。实际上被装饰器修饰的函数在执行的时候不会直接运行其函数内部的逻辑,而是先将这个函数当作参数传递给装饰器函数,再执行装饰器函数的逻辑。

## 定义装饰器函数
def decorator(func):
    print("正在执行装饰器函数,即将执行被修饰的 %s 函数" % func.__name__)

    ## 执行被修饰的函数funA(),其实这里是先将funA()函数赋值给了func形参变量,等价于:func = funA
    func_result = func()
    print(func_result)  ## 得到funA()函数的返回结果值:funA执行完毕

    # return "装饰器执行完毕"  ## 如果装饰器返回的是'普通变量',那么被修饰的'函数名'就变成了变量名
    return func  			  ## 如果装饰器返回的是一个'函数',那么被修饰的函数名依然表示一个函数

## 定义被装饰的函数
@decorator
def funA():
    print("正在执行funA()函数")
    return "funA执行完毕"


1.当装饰器返回的是'普通变量',打印被装饰器修饰的函数名
print(funA)
## 打印内容为:装饰器执行完毕

2.当装饰器返回的是'普通变量',打印被装饰器修饰的函数方法
print(funA())  
## 报错,TypeError: 'str' object is not callable,是由于装饰器返回的是一个字符串变量,
## 无法当作函数来调用。

3.当装饰器返回的是'函数',打印被装饰器修饰的函数名
print(funA)    
## 打印内容为:<function funA at 0x0000020DC3451A80>,
## 这里其实是将装饰器最初接收的被装饰的函数(也就是funA()函数)重新赋值给funA变量,
## 等价于:funA = func 

4.当装饰器返回的是'函数',打印被装饰器修饰的函数方法
print(funA())  
## 打印内容为:正在执行funA()函数
## 			  funA执行完毕
## 这里重新调用了一次funA()函数的内容

二、带参数的函数装饰器

当被装饰器修饰的函数不带参数时,可以将被修饰的函数直接赋值给装饰器函数的形参,然后传递到装饰器中去执行。但是当被修饰的函数带参数时,由于装饰器函数的形参只能接收被修饰的函数,无法接收被修饰的函数的参数,要如何将这些参数也传入到构造器中使用呢?其实需要在装饰器中添加一个嵌套函数,保证这个嵌套函数的形参个数与被修饰的函数形参个数相同就好。由于装饰器可以修饰任意函数,而每个函数的形参个数可能也都大不相同,所以不可能每次都去修改装饰器的嵌套函数,因此我们使用python的变长参数*args和**kwargs来解决不同函数参数不同的问题。

  1. 被装饰器修饰的函数只有一个参数时:
## 定义装饰器
def decorator(func):
    print("正在执行装饰器函数,即将执行被修饰的 %s 函数" % func.__name__)

    ## 定义一个嵌套函数,用来接收被装饰函数的参数
    def deco_func(deco_param):
        print("装饰器函数内嵌套函数接收的参数值为:%s " % deco_param)
        ## 将接收的被装饰的函数参数重新传递给被装饰函数并执行被装饰的函数后将被装饰的函数返回值返回出去
        return func(deco_param)  

    print("装饰器函数内嵌套函数对象为: %s" % deco_func)
    ## 装饰器函数最终将嵌套函数对象返回出去
    return deco_func

## 定义被装饰的函数
@decorator
def funA(param):
    print("正在执行funA()函数, 参数为:%s " % param)
    return "funA执行完毕"


print("funA对象为: %s" % funA)
正在执行装饰器函数,即将执行被修饰的 funA 函数
装饰器函数内嵌套函数对象为: <function decorator.<locals>.deco_fucn at 0x0000022F83E22B60>
funA对象为: <function decorator.<locals>.deco_fucn at 0x0000022F83E22B60>

1.通过打印funA函数名变量可以看出,funA = deco_func,实际上就是执行完装饰器函数后,最终直到将这个嵌套函数deco_func对象返回出去并赋值给funA函数名变量。

print("调用funA函数,执行结果为: %s" % funA("sdad"))
正在执行装饰器函数,即将执行被修饰的 funA 函数
装饰器函数内嵌套函数对象为: <function decorator.<locals>.deco_fucn at 0x000001BA245FE340>
装饰器函数内嵌套函数接收的参数值为:sdad 
正在执行funA()函数, 参数为:sdad 
调用funA函数,执行结果为: funA执行完毕

2.通过funA函数名变量去调用实际赋值给它的函数,也就是会进入到装饰器中去执行deco_func()函数,
再执行嵌套函数的时候会调用func()函数(其实就是执行funA()函数),最后将func()函数执行结果返回出去。
  1. 被装饰器修饰的函数有多个参数时:
def decorator(func):
    print("正在执行装饰器函数,即将执行被修饰的 %s 函数" % func.__name__)

    ## 定义一个嵌套函数,用来接收被装饰函数的参数
    def deco_fucn(*args, **kwargs):
        print("装饰器函数内嵌套函数接收的参数值为:%s %s " % (args, kwargs))
        return func(*args, **kwargs) 
        
    print("装饰器函数内嵌套函数对象为: %s" % deco_fucn)
    return deco_fucn


@decorator
def funA(param):
    print("正在执行funA()函数, 参数为:%s " % param)
    return "funA执行完毕"


@decorator
def funB(param1, param2):
    print("正在执行funB()函数, 参数为:%s %s " % (param1, param2))
    return "funB执行完毕"

print("调用funA函数,执行结果为: %s" % funA("sdad"))
print("调用funB函数,执行结果为:%s " % funB("A", "B"))

在这里插入图片描述

三、装饰器函数修饰类

装饰器函数除了可以装饰函数外,还可以装饰在类上,当装饰在类上时,可以实现对类添加额外的属性、方法等,被装饰的类同样分为带参数与不带参数,实现方式与装饰在函数上一样的。

## 定义一个装饰器函数,cls形参接收的是被装饰的类对象
def decorator(cls):
    ## 通过装饰器内嵌套函数
    def wrapper():
    	## 给被装饰的类添加一个属性
        cls.age = 18
        ## 嵌套函数返回的是被装饰的类对象
        return cls
	
	print("装饰器函数内嵌套函数对象为: %s" % wrapper)
	## 装饰器函数最终返回的是嵌套函数对象
    return wrapper


@decorator 
class Person:
    def __init__(self, name):
        self.name = name

print("被装饰的类名变量为: %s" % Person)
装饰器函数内嵌套函数对象为: <function decorator.<locals>.wrapper at 0x00000191FC6204A0>
被装饰的类名变量为: <function decorator.<locals>.wrapper at 0x00000191FC6204A0>

1.通过打印类名变量可以看出,Person = wrapper;实际上就是执行完装饰器函数后会将装饰器最终返回的嵌套函数对象赋值给类名变量Person。

print(Person().age)
18

2.通过类名变量Person去调用实际赋值给它的函数对象,也就是进入装饰器中去执行嵌套函数wrapper(),执行完嵌套函数后会返回一个cls对象,也就是被装饰器修饰的Person对象,这里Person()得到就是经过装饰器中的嵌套函数处理过后,具有age属性的Person对象了。

四、装饰器类装饰函数

类也可以用来当作装饰器,在使用类装饰器的时候需要注意在装饰器类内需要实现__init__()和__call__()方法。

class Decorate:
    ## func接收被装饰的函数
    def __init__(self, func):
        print("__init__中接收的被装饰的函数名为:%s " % func.__name__)
        self.__func = func

    ## *args, **kwargs 接收被装饰的函数的参数
    def __call__(self, *args, **kwargs):
        """实现__call__方法,对被装饰的函数参数进行打印或者写入到日志中"""
        print("打印被装饰的函数参数为:%s %s " % args, kwargs)

        ## 调用被装饰的函数并将执行的结果返回
        return self.__func(*args, **kwargs)


@Decorate
def sum(a, b):
    result = a + b
    return result

print(sum)
<__main__.Decorate object at 0x0000024E98556CD0>
##这里打印被装饰的函数名变量得到的是装饰器类的对象。

print(sum(1, 2))
__init__中接收的被装饰的函数名为:sum 
打印被装饰的函数参数为:1 2  {}
3
##其实这里sum(1, 2)已经不能说是调用sum函数了,实际上是调用Decorate类的构造方法,
##将sum()函数传递到装饰器类的__init__方法,被装饰的函数的参数传递到装饰器类的__call__方法中。

五、装饰器类装饰类

与装饰器类装饰函数修饰函数一样,只不过被装饰器装饰的是一个类,基本处理逻辑是一样的。

class Decorator:

    ## decorated_class接收被装饰的类
    def __init__(self, decorated_class):
        self.decorated_class = decorated_class

    ## *args, **kwargs用于接收被装饰的类的参数
    def __call__(self, *args, **kwargs):
        # 对被装饰的类执行初始化,并返回创建的实例对象
        return self.decorated_class(*args, **kwargs)


@Decorator
class Persion:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def get_age(self):
        return self.age

print(Persion)
<__main__.Decorator object at 0x0000021887015210>
##这里还是将装饰器类对象赋值给被装饰的类名变量。

obj = Persion("Tom", 18)
##由于Persion已经被赋值成装饰器类对象,所以Persion("Tom", 18)实际上调用的是装饰器中的__call__方法
##在装饰器的__call__方法中对被装饰的类执行初始化,并返回创建的实例对象,所以obj相当于初始化后的Persion对象
print(obj.get_age())
18

六、多装饰器的执行顺序

有时会遇到多个装饰器同时装饰一个函数的场景,其装饰顺序与执行顺序为:离原函数越近的装饰器先进行装饰,离原函数越远的装饰器先执行。

def Decorate1(func):
    def wrapper1(*args, **kwargs):
        print(func.__name__)
        print("Decorate1执行时间为: ", datetime.datetime.now())
        time.sleep(5)
        return func(*args, **kwargs)

    return wrapper1


def Decorate2(func):
    def wrapper2(*args, **kwargs):
        print(func.__name__)
        print("Decorate2执行时间为: ", datetime.datetime.now())
        time.sleep(5)
        return func(*args, **kwargs)

    return wrapper2


@Decorate1  # 等价于 func = decorator1(decorator2(func))
@Decorate2  # 等价于 func = decorator2(func)
def func():
    print("原函数执行时间为: ", datetime.datetime.now())

func()
wrapper2
Decorate1执行时间为:  2023-06-28 23:10:41.802692
func
Decorate2执行时间为:  2023-06-28 23:10:46.803709
原函数执行时间为:  2023-06-28 23:10:51.804347

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

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

相关文章

【企业架构治理】SOGAF ,Salesforce 的运营、治理和架构框架

“如果你想要新的东西&#xff0c;你必须停止做旧的东西。”——彼得德鲁克&#xff0c;《公司概念》的作者 这篇文章介绍了 Salesforce 运营、治理和架构框架 (SOGAF)&#xff0c;这是一个新的大规模治理框架&#xff0c;由对跨多个行业的学术文献、现有框架和转型案例研究的广…

如何对数据库进行垂直拆分或水平拆分?

水平拆分的意思&#xff0c;就是把一个表的数据给弄到多个库的多个表里去&#xff0c;但是每个库的表结构都一 样&#xff0c;只不过每个库表放的数据是不同的&#xff0c;所有库表的数据加起来就是全部数据。水平拆分的意 义&#xff0c;就是将数据均匀放更多的库里&#xff0…

高性能计算培训机构哪些好?培训机构排行推荐!

目前市面上高性能计算培训不多&#xff0c;尤其是专业做高性能计算培训的机构更是不多。 比较好的高性能计算培训有北大未名超算队、各类超算中心的高性能计算培训视频、以及猿代码科技的系统实战化的高性能计算培训课程&#xff0c;这些在B站上均有不错的播放量&#xff0c;群…

【Go语言从入门到精通系列-基础篇】Go语言变量、常量和运算符:完全指南

系列文章目录 【Go语言从入门到精通系列-基础篇】Go安装 语言特性&#xff0c;以及开启你人生中的第一个go程序 【Go语言从入门到精通系列-基础篇】Go语言包的管理以及基础语法与使用。 Go语言从入门到精通系列-基础篇 系列文章目录前言一、变量和常量的基本概念1. 变量1.1 变…

赶赴一场夏日盛宴丨千岛湖夏季团建旅行攻略

千岛湖的秀丽景色让人惊艳&#xff0c;也是江浙沪地区热门的团建目的地之一&#xff1b; 千岛湖 千岛湖团建元素&#xff1a;【千岛湖风景区】【山顶观景台】【皮划艇】【环湖骑行】【卡丁车】【高空闯关】【篝火晚会】【湖畔烧烤】【高空跳伞】 【千岛湖山顶观景台】 站在湖畔…

如何用快改图工具指定压缩图片大小

我们在遇到好看的图片或自己拍的照片&#xff0c;都会对其进行一些处理&#xff0c;处理完成后将其保存&#xff0c;保存时发现自己要存的图片太多了&#xff0c;比较占内容。这种情况就需要压缩。那么&#xff0c;在压缩图片时&#xff0c;怎么压缩图片到指定大小呢&#xff1…

【ES三周年】| 基于国产化操作系统搭建ELK日志分析平台

引入 鲲鹏认证-Kylin麒麟操作系统-ELK日志分析平台 开篇 何为ELK Stack&#xff1f;它又能够给我们带来什么&#xff1f; 综述 ELK为三个开源项目的首字母缩写&#xff0c;分别对应是&#xff1a;Elasticsearch、Logstash、Kibana&#xff0c;由这三个软件及其相关的组件可…

threejs 入门 (vite + vue3)

threejs threejs用于实现3D效果 vite创建vuejs项目 npm create vite选择vue 和js创建vue3项目 安装threejs npm install three运行项目 cd project npm i npm run dev修改App.vue 创建一个场景和立方体&#xff08;Creating a scene&#xff09; <script setup> …

labview 波形图表(waveform Chart)

波形图表&#xff08;waveform Chart&#xff09;是显示一条或多条曲线的特殊数值控件&#xff0c; 一般用于显示以恒定速率采集的数据。 波形图表会在缓冲区保留历史数据并在历史数据后添加新数据。 波形图表的默认数据缓冲区大小为1024个数据点。 &#xff08;右击波形图…

BurpSuite使用教程·代理抓包篇

BurpSuite使用教程代理抓包篇 1.Burp Suite代理和浏览器设置2.数据拦截与控制3.可选项配置4.Proxy监听设置1.Burp Suite代理和浏览器设置 Burp Suite代理工具是以拦截代理的方式,拦截所有通过代理的网络流量,如客户端的请求数据、服务器端的返回信息等。Burp Suite主要拦截h…

Mybatis源码分析_事务管理器 (5)

今天我们首先学习2个设计模式。工厂设计模式 和 模板设计模式 工厂方法模式&#xff1a; 定义一个用于创建对象的接口&#xff0c;让子类决定实例化哪一个类。工厂方法使一个类实例化延迟到子类。 上图的图很清晰&#xff1a;业务接口有很多实现类&#xff0c;而工厂接口也有还…

保姆级自动化测试教程(Selenium+java)

文章目录 自动化测试1. 自动化测试介绍2. Selenium介绍与环境搭建2.1 介绍2.2 Selenium原理2.3 SeleniumJava环境搭建 3. webdriver API3.1 元素的定位3.2 操作测试对象3.3 添加等待3.4 打印信息3.5 浏览器操作3.6 键盘事件3.7 鼠标事件3.9 特殊使用3.10 浏览器关闭3.11 切换窗…

让你不再好奇ai绘画生成器有哪些

绘画爱好者李明最近遇到了一个难题&#xff1a;他需要在短时间内完成一幅复杂的数字油画作品&#xff0c;但是他的手绘技能仍然不够熟练。于是&#xff0c;他想到了借助一些ai绘画软件来帮助他进行绘画创作。那么&#xff0c;你知道ai智能绘画软件有哪些吗&#xff1f;接下来我…

Vue中使用ElementUItable表格显示图片问题

1.问题 说明&#xff1a;table表格显示不了图片问题 品牌logo中显示的是url地址&#xff0c;因此我们要使用作用域插槽。 <template slot scope"{row,$index}"> </template> 说明&#xff1a;使用Vue的插槽功能&#xff0c;允许在当前列的内容上添加…

git暂存功能(只需要两步)

使用场景 我在写一个功能的时候发现一个bug&#xff0c;但是这个bug在我上一次提交的时候好像是没有的&#xff0c;所有我需要把当前的代码暂存,然后查看上一次提交是否正常。 一、暂存 暂存当前所有的修改 git stash二、恢复 恢复最近一次暂存&#xff0c;并把暂存删掉 …

获阿布扎比政府11亿美元投资将使蔚来改变电动汽车行业游戏规则?

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 总结 &#xff08;1&#xff09;6月20日&#xff0c;蔚来宣布与阿布扎比政府控股的投资公司CYVN Holdings签订了股份认购协议&#xff0c;蔚来将获得CYVN Holdings11亿美元的战略投资&#xff0c;这可能会导致蔚来在阿布扎…

从零开始 Spring Boot 50:Entity Lifecyle Event

从零开始 Spring Boot 50&#xff1a;Entity Lifecyle Event 图源&#xff1a;简书 (jianshu.com) 在上篇文章&#xff0c;我介绍了 Hibernate 中的实体生命周期以及可以转换实体状态的 Session API。就像 Spring Bean 的生命周期拥有一些事件&#xff0c;通过监听这些事件我们…

C# 中【委托】的简单理解

先不说 C# 中的委托编程&#xff0c;先从生活中的例子入手。 场景一&#xff1a; 防疫期间&#xff0c;外卖人员不能进入花园小区。外卖小哥到了花园小区门口&#xff0c;只好【委托】花园的保安人员&#xff0c;把东西送上楼去。 场景二&#xff1a; 有资格的人&#xff0c;都…

慢谈漫威--来龙去脉

慢谈漫威 背景介绍一、抛开电影看漫威相关公司背景、关系二、漫威宇宙和索尼漫威宇宙三、 漫威宇宙四、话外--美国6大影视公司 背景介绍 最近在看一部漫威电影&#xff0c;可能是年纪大了&#xff0c;看美国英雄大片以及系列电影提不起兴趣&#xff0c;各种人物背景关系也不清…

途乐证券|光伏概念发力上扬,好利科技涨停,隆基绿能等拉升

光伏概念28日盘中发力上扬&#xff0c;截至发稿&#xff0c;露笑科技、好利科技、宁波能源等涨停&#xff0c;中国动力、欧晶科技、聚合材料、鹿山新材等涨超5%&#xff0c;帝科股份、隆基绿能、福斯特等涨超3%。 露笑科技昨日晚间披露的半年度业绩预告&#xff0c;预计上半年归…