Python 装饰器详解(中)
Python 装饰器详解(中)
转自:https://blog.csdn.net/qq_27825451/article/details/84581272,博主仅对其中 demo 实现中不适合python3 版本的语法进行修改,并微调了排版,本转载博客全部例程博主均已亲测可行。
Python 3.8.5
ubuntu 18.04
声明:此文章为,python装饰器详解——中篇,上一篇文章中,即详解装饰器——上篇 ,已经详细讲解了装饰器诞生的背景,装饰器的定义、作用、应用场景,本文将以实际例子为依托,深入详解装饰器的各类实现(包括函数装饰器、类装饰器、闭包、装饰器的嵌套四大块内容)系列文章共分为 上、中、下 三篇。此为第二篇。
一、函数装饰器
前面提到过,装饰器分为函数装饰器、类装饰器,本节详细解释函数装饰器,又分为两种情况,因为函数装饰器可以装饰函数,也可以装饰类。函数装饰器是最常见的,故而最先讨论。
注意:因为没有参数,且无函数返回值的函数是最为简单的,就如“上篇”所讨论的那样,这里就不再叙述了,本文主要讲的都是函数带有参数,而且具有函数返回值的函数。
1. 函数装饰器装饰一个函数
假定有一个函数实现两个数的相加,a+b,但是为了对这两个数相加的结果进行加密,我们需要给函数添加额外的代码,但是我们通过装饰器去实现,要达到的效果是,不是直接返回a+b的结果,而是进行进一步处理。代码如下:
def MethodDecoration(function): #外层decoratorc=150d=200def wrapper(a,b): #内层wrapper。和add_function参数要一样result=function(a,b)result=result*c/d #加密,相当于添加额外功能return result #此处一定要返回值return wrapper@MethodDecoration
def add_function(a,b):return a+bresult=add_function(100,300) #函数调用
print(result)
运行结果为:300.0 ,即(100+300)*150/200。
因为函数装饰器去装饰函数最为常见,所以这里就不多再解释了,按照前面上篇所讲的模板来即可,但是因为被装饰的函数有参数,而且具有返回值,有两个点需要注意的:
wrapper
需要保证与add_function
参数一致。因为返回的wrapper
就是add_function
,所以要统一,我们可以使用*arg
和**kwargs
去匹配任何参数;wrapper
一定要返回值。因为add_function函数是需要返回值的。
2. 函数装饰器装饰一个类
在Python中,从某种意义上来说,函数和类是一样的,因为它们都是对象(python一切皆对象),故而decorator的参数理所当然也可以传入一个类了。其中最经典的应用,就是使用装饰器构造“单例模式”(不明白单例模式的小伙伴可以参见下面这篇博文哦)
一文详解“单例模式”及其python语言的实现
代码如下:
def Singleton(cls): #这是第一层函数,相当于模板中的Decorator.目的是要实现一个“装饰器”,而且是对类型的装饰器'''cls:表示一个类名,即所要设计的单例类名称,因为python一切皆对象,故而类名同样可以作为参数传递'''instance = {}def singleton(*args, **kwargs): #这是第二层,相当于wrapper,要匹配参数if cls not in instance:instance[cls] = cls(*args, **kwargs) #如果没有cls这个类,则创建,并且将这个cls所创建的实例,保存在一个字典中return instance[cls] #返回创建的对象return singleton@Singleton
class Student(object):def __init__(self, name,age):self.name=nameself.age=ages1 = Student('张三',23)
s2 = Student('李四',24)
print((s1==s2))
print(s1 is s2)
print(id(s1),id(s2),sep=' ')
运行结果为:
True
True
140506831296352 140506831296352
懂得单例模式的小伙伴可能一看就明白了,上面的实现和我前面讲过的“装饰器模板”,基本上一样,第一层、第二层、返回值、参数匹配等。但是有的小伙伴可能会问,这里我没有看到“添加额外功能”啊,装饰器不是添加额外功能的么?实际上“添加额外功能”是一种抽象的表述,不是说一定要添加什么东西,对被装饰的对象(函数、类)进行某种约束、处理、添加、删减等额外操作统称为添加额外功能。
这里约束了这个类型Student的创建,主要这个类还没有创建实例,就创建一个,只要创建了,就不在创建新的实例了,只需要把之前创建的返回即可,这是单例模式的思想。如果还是不明白,下面再举一个“添加额外功能”的例子。
比如我有一个学生类,在创建学生实例的时候有两个实例属性,name,age,现在要通过装饰器对类加以装饰,使得在创建学生类的实例的时候,还会添加height和weight两个属性,代码如下:
def ClassDecorator(cls): #第一层函数decoratorheight=170weight=65def wrapper(name,age): #第二层函数wrapper,参数要和类的构造函数匹配s=cls(name,age)s.height=height #添加两个额外属性s.weight=weightreturn s #返回创建的对象,因为类的构造函数是要返回实例的,即有返回值return wrapper@ClassDecorator
class Student:def __init__(self,name,age):self.name=nameself.age=agestu=Student('张三',25)
print(stu.name)
print(stu.age)
print(stu.height) #在 IDE中可能会有提示此处错误,学生没有height和weight属性,但是运行之后没错
print(stu.weight) #这就是python的魅力,动态添加属性
运行结果为:
张三
25
170
65
上面的例子和我们前面讲的装饰函数实在是太像了,基本上和前面讲的模板一模一样。
**总结:**函数装饰器不管是装饰函数、还是装饰类,所遵循的思想原理是一样的,实现的方式也是大同小异。注意,函数装饰器装饰类,实际上是装饰类的构造函数哦!
二、类装饰器
前面定义的装饰器都是函数,实际上,类也可以是一个装饰器,同样的道理,类装饰器既可以装饰函数,也可以装饰类。
1. 类装饰器装饰函数
先从一个简单的例子说起,代码如下:
class MethodDecorator:def __init__(self,function):self.function=functiondef __call__(self):print('开始')self.function()print('结束')@MethodDecorator
def myfunc():print('我是函数myfunc')myfunc()
运行结果为:
开始
我是函数myfunc
结束
可能有的小伙伴很懵逼,怎么会得到这样的结果?我们一句一句来分析。
@MethodDecorator
def myfunc():print('我是函数myfunc')myfunc()
这里相当于 myfunc=MethodDecorator(myfunc)
,这样一写就明白了,首先myfunc
函数作为参数传递给类的构造函数,因为调用类的构造函数自然会返回类的一个实例对象,所以前面的myfunc
实际上是类的一个实例对象,然后调用myfunc
,这里虽然从形式上看依然是看起来还是调用函数,但是本质上已经发生了变化,它实际上一个对象调用(这是类装饰器的本质,很重要),这就是为什么要定义__call__
魔法方法。下面比如函数有返回值,而且有参数,要用类装饰器去装饰这个函数,再用一个实例说明,依然以上面的两个数据值和进行加密为例。
class MethodDecorator:def __init__(self,function): #这里相当于是第一层,作用是将函数function传递进来self.function=functionself.c=150 #这两个是需要加密的额外信息self.d=200def __call__(self,a,b): #这相当于是第二层的wrapperprint('开始')result=self.function(a,b)result=result*self.c/self.dprint('结束')return result #返回值@MethodDecorator
def add_function(a,b):return a+bresult=add_function(100,300) #这里相当于是函数调用
print(result)
运行结果为:
开始
结束
300.0
总结:实际上类装饰器所实现的功能在原理上和函数装饰器也没有太大的区别,但是在代码实现上有所区别,主要体现在两方面:
类装饰器的构造函数
__init__
就相当于是第一层(外层)的decorator
,传入需要装饰的对象作为参数;类装饰器的魔法方法
__call__
相当于是第二层(内层)的wrapper
。注意参数要统一,有返回值需要返回值。
2. 类装饰器装饰类
依然以上面的给学生添加额外属性为例
class ClassDecorator:def __init__(self,cls): #这里相当于是第一层,作用是将类名Student传递进来self.cls=clsself.height=170self.weight=65def __call__(self,name,age): #这相当于是第二层的wrappers=self.cls(name,age)s.height=self.height #动态添加属性,增加额外信息s.weight=self.weightreturn s #返回创建的学生实例s@ClassDecorator
class Student:def __init__(self,name,age):self.name=nameself.age=agestu=Student('张三',25) #注意:这里的Student其实并不是一个类了,而是装饰器返回的一个对象,即这里的Student是ClassDecorator的实例#而且,这里的Student('张三',25) 也不是构造函数了,它的本质是装饰类的“对象调用”
print(stu.name)
print(stu.age)
print(stu.height)
print(stu.weight)
输出结果为:
张三
25
170
65
总结:这里的Student其实并不是一个类了,而是装饰器返回的一个对象,即这里的Student是ClassDecorator的实例,而且,这里的Student(‘张三’,25) 也不是构造函数了,它的本质是装饰类的“对象调用”。
三、类装饰器的一般模板
通过上面的例子,不管类装饰器是装饰类,还是装饰函数,它的模板都是大同小异的,如下所示:
class ClassDecorator: #类装饰器的名称def __init__(self,function_or_cls): #这里相当于是第一层,作用是将需要装饰的类名、或者是函数名传递进来#这里可以添加额外信息self.cls=cls #或者是self.function=function,本质是要构造 一个属性#这里可以添加额外信息def __call__(self,name,age): #这相当于是第二层的wrapper,参数需要与被装饰的类、被装饰的函数,参数相同#这里可以增加额外信息s=self.cls(name,age) #本质是调用原来的函数或者类的构造函数#result=self.function(a,b) #这里可以增加额外信息return s #返回创建的学生实例s
注意:类装饰器,对象调用__call__
是不可或缺的哦。
四、装饰器的缺点
前面讲了一大堆装饰器的优点:简化代码,代码复用;增加额外功能等。那么装饰器优缺点吗,当然有了,世界上就没有完美无缺的东西!那到底有一些什么样的缺点呢?其实在上面的表述中已经提到了,不知道小伙伴有没有注意!
(以下代码在上面的代码代码基础上执行,上面代码这里就不重复一遍了)
1. 函数装饰器装饰函数的时候
在上面源代码的基础之上添加下面的代码:
print(add_function.__name__)
输出为:
wrapper
这是为什么,如果add_function
没有被装饰器修饰的话,则返回的应该为add_function
,这里为什么会返回第二层包装函数wrapper
的名称?这是因为装饰器的本质是add_function=MethodDecoration(add_function)
,而MethodDecoration
返回的本来就是wrapper
,这就是上面结果的解释了。
2. 函数装饰器装饰类的时候
同样添加一句代码
print(Student.__name__)
返回的结果是:wrapper
出现这个现象的原因同上面1中所述的,完全一样。
3. 类装饰器装饰类的时候
同样的添加下面一句话
print(add_function.__name__) #这里IDE不会提示错误哦,IDE依然觉得这是个函数,应该有__name__才对的
显示:
AttributeError: 'MethodDecorator' object has no attribute '__name__'
这里为什么突然不一样了呢?正如前面所说的,这里的add_function
本质上是add_function=MethodDecorator(add_function)
,所以add_function
本质上是装饰类的一个实例,而MethodDecorator
没有定义__name__
属性,那自然调用add_function.__name__
就会显示没有__name__
这个属性了。
4. 类装饰器装饰类的时候
print(Student.__name__) #这里IDE不会提示错误哦,IDE依然觉得这是个类名,应该有__name__才对的
显示:
AttributeError: 'ClassDecorator' object has no attribute '__name__'
原因同上面一样,因为Student本质上是ClassDecorator的一个对象实例哦!
装饰器的缺点总结:
被函数装饰器所装饰的对象(函数、类)已经不再是它本身了,虽然从形式上看没有变化,本质上是函数装饰器的内部wrapper;
被类装饰器所装饰的对象(函数、类)也不再是它本身了,虽然从形式上看没有变化,本质上是类装饰器的一个对象。
补充:关于装饰器的嵌套,装饰器与python闭包的关系,我会在系列文章:Python高级编程——装饰器Decorator详解(下篇)中继续讲解,有兴趣的继续关注!
Python 装饰器详解(中)相关推荐
- Python 装饰器详解(下)
Python 装饰器详解(下) 转自:https://blog.csdn.net/qq_27825451/article/details/84627016,博主仅对其中 demo 实现中不适合pyth ...
- Python 装饰器详解(上)
Python 装饰器详解(上) 转自:https://blog.csdn.net/qq_27825451/article/details/84396970,博主仅对其中 demo 实现中不适合pyth ...
- python装饰器详解-python中的装饰器详解
在了解装饰器的之前一定要先了解函数作为参数传递, 什么是函数内嵌,请参考我之前写的博客函数简介 因为在python里面,函数也是对象,也可以作为参数进行传递.python装饰器本质也是一种特殊函数,它 ...
- Python装饰器详解,详细介绍它的应用场景
装饰器的应用场景 附加功能 数据的清理或添加: 函数参数类型验证 @require_ints 类似请求前拦截 数据格式转换 将函数返回字典改为 JSON/YAML 类似响应后篡改 为函数提供额外的数据 ...
- python装饰器详解-Python装饰器基础概念与用法详解
本文实例讲述了Python装饰器基础概念与用法.分享给大家供大家参考,具体如下: 装饰器基础 前面快速介绍了装饰器的语法,在这里,我们将深入装饰器内部工作机制,更详细更系统地介绍装饰器的内容,并学习自 ...
- python装饰器详解51-python装饰器使用实例详解
这篇文章主要介绍了python装饰器使用实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 python装饰器的作用就是在不想改变原函数代码的情 ...
- python装饰器详解-python装饰器使用实例详解
这篇文章主要介绍了python装饰器使用实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 python装饰器的作用就是在不想改变原函数代码的情 ...
- python装饰器作用-Python装饰器详解
1.闭包函数 在看装饰器之前,我们先来搞清楚什么是闭包函数.python是一种面向对象的编程语言,在python中一切皆对象,这样就使得变量所拥有的属性,函数也同样拥有. 这样我们就可以理解在函数内创 ...
- python类装饰器详解-Python 装饰器详解
开放封闭原则: 开放对扩展 封闭修改源代码 改变了人家调用方式 装饰器结构 """ 默认结构为三层!!!每层返回下一层内存地址就可以进行执行函数, 传参:语法糖中的传参可 ...
最新文章
- 多分辨率适配常用目录
- Dispatch 方法简介
- 还没毕业就被阿里30万年薪抢定,他凭什么?
- spring中配置quartz定时器
- 百度地图LBS云存储自定义poi热点
- 水溶彩铅的特点技法运用
- 【机器学习系列】EM算法第一讲:EM算法相关概述及收敛性证明
- 关于vs2017如何配置和运行龙书DX9案例
- python数字字母识别_字符图像识别——数字字母混合
- 【n卡】GeForce Experience 的额外功能
- 箭头函数及其this指向
- 马克思主义基本原理概论-考点串讲
- 我的第一本社会心理学(part4)--自我偏差
- 低功耗蓝牙中的蓝牙地址和隐私
- 字节跳动岗位薪酬体系曝光,看完感叹:不服不行,想高薪还得是学这个。。。。
- 垂暮黄昏——回顾CSP2021
- C语言解决经典问题约瑟夫环--数组
- apache安装过程
- 【Spring系列04】自动装配(Qualifier,Autowired,Resource讲解)
- 指向性麦克风--心形麦克风设计(五)
热门文章
- TortoiseGit 基础5部曲
- 服务器ip映射修改_集群高可用篇_01
- MyBatis-Plus_AR 模式
- 微信小程序---实现输入手机验证码功能
- Vue+mui实现图片的本地缓存
- Java-For循环
- IOS – OpenGL ES 调节图像灰色 GPUImageGrayscaleFilter
- 华为鸿蒙用着怎么样,首批华为鸿蒙用户体验如何?“差评”有点难听,但是很真实!...
- android 主流分辨率是多少,android 屏幕分辨率问题
- python 网络运维框架scape_“python scape 教程“求PhotoScape X Pro for Mac软件