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。

因为函数装饰器去装饰函数最为常见,所以这里就不多再解释了,按照前面上篇所讲的模板来即可,但是因为被装饰的函数有参数,而且具有返回值,有两个点需要注意的

  1. wrapper需要保证与add_function参数一致。因为返回的wrapper就是add_function,所以要统一,我们可以使用*arg**kwargs去匹配任何参数;

  2. 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

总结:实际上类装饰器所实现的功能在原理上和函数装饰器也没有太大的区别,但是在代码实现上有所区别,主要体现在两方面:

  1. 类装饰器的构造函数__init__就相当于是第一层(外层)的 decorator,传入需要装饰的对象作为参数;

  2. 类装饰器的魔法方法__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的一个对象实例哦!

装饰器的缺点总结

  1. 被函数装饰器所装饰的对象(函数、类)已经不再是它本身了,虽然从形式上看没有变化,本质上是函数装饰器的内部wrapper;

  2. 被类装饰器所装饰的对象(函数、类)也不再是它本身了,虽然从形式上看没有变化,本质上是类装饰器的一个对象。

补充:关于装饰器的嵌套,装饰器与python闭包的关系,我会在系列文章:Python高级编程——装饰器Decorator详解(下篇)中继续讲解,有兴趣的继续关注!

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

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

相关文章

存储型xss案例

存储型xss原理: 攻击者在页面插入xss代码,服务端将数据存入数据库,当用户访问存在xss漏洞的页面时,服务端从数据库取出数据展示到页面上,导致xss代码执行,达到攻击效果 案例: 在一个搭建的论坛网站中, 根据存储型xss注入的条件,要找到可以存储到数据库的输入位置,并且这个位置…

反射型XSS案例

**原理:**攻击者将url中插入xss代码,服务端将url中的xss代码输出到页面上,攻击者将带有xss代码的url发送给用户,用户打开后受到xss攻击 需要url中有可以修改的参数 案例: 可能存在反射型xss的功能(点) : 搜索框等(所有url会出现参数的地方都可以尝试)……

Python 装饰器详解(下)

Python 装饰器详解(下) 转自:https://blog.csdn.net/qq_27825451/article/details/84627016,博主仅对其中 demo 实现中不适合python3 版本的语法进行修改,并微调了排版,本转载博客全部例程博主均已亲测可行…

xss-lab靶场通关writeup(1~6.......在更新)

level 2 : 标签被编码&#xff0c;利用属性完成弹窗 输入 发现没有弹窗 查看源代码&#xff1a; 发现&#xff1a; <>符号被编码 说明keybord参数进行了处理&#xff0c;那么只能从属性上进行恶意编码&#xff1a;先将属性的引号和标签闭合&#xff0c;用 // 将后面的…

PyTorch 分布式训练DDP 单机多卡快速上手

PyTorch 分布式训练DDP 单机多卡快速上手 本文旨在帮助新人快速上手最有效的 PyTorch 单机多卡训练&#xff0c;对于 PyTorch 分布式训练的理论介绍、多方案对比&#xff0c;本文不做详细介绍&#xff0c;有兴趣的读者可参考&#xff1a; [分布式训练] 单机多卡的正确打开方式…

Linux free 命令详解

Linux free 命令详解 free 命令用来查看系统中已用的和可用的内存。 命令选项及输出简介 关于各种命令的功能和命令选项&#xff0c;还是推荐英语比较好的同学直接看手册 RTFM&#xff1a;man free。这里简单总结一下一些重点&#xff1a; 功能及输出简介 free 命令显示系…

CTF web题 wp:

1.签到题 火狐F12查看源码&#xff0c;发现注释&#xff1a; 一次base64解码出flag 2.Encode 在这里插入图片描述 和第一题界面一样&#xff1f;&#xff1f; 轻车熟路f12&#xff1a; 发现编码&#xff1a; 格式看上去是base64&#xff0c;连续两次base64后&#xff0c;观…

【深度学习】深入理解Batch Normalization批归一化

【深度学习】深入理解Batch Normalization批归一化 转自&#xff1a;https://www.cnblogs.com/guoyaohua/p/8724433.html 这几天面试经常被问到BN层的原理&#xff0c;虽然回答上来了&#xff0c;但还是感觉答得不是很好&#xff0c;今天仔细研究了一下Batch Normalization的原…

ThinkPHP V5 漏洞利用

ThinkPHP 5漏洞简介 ThinkPHP官方2018年12月9日发布重要的安全更新&#xff0c;修复了一个严重的远程代码执行漏洞。该更新主要涉及一个安全更新&#xff0c;由于框架对控制器名没有进行足够的检测会导致在没有开启强制路由的情况下可能的getshell漏洞&#xff0c;受影响的版本…

Vim 重复操作的宏录制

Vim 重复操作的宏录制 转自&#xff1a;https://www.cnblogs.com/ini_always/archive/2011/09/21/2184446.html 在编辑某个文件的时候&#xff0c;可能会出现需要对某种特定的操作进行许多次的情况&#xff0c;以编辑下面的文件为例&#xff1a; ; ;This is a sample config…

Vim 进阶1

Vim 进阶1 所有你觉得简单重复&#xff0c;可以自动化实现的操作&#xff0c;都是可以自动化实现的。 Vim光标移动拾遗 w&#xff1a;下一个单词的开头&#xff0c;e&#xff1a;下一个单词的结尾&#xff0c;b&#xff1a;上一个单词的开头&#xff0c; 0&#xff1a;行首…

攻防世界web题ics-06(爆破id值)

打开界面&#xff1a;嚯&#xff01;这花里胡哨 点来点去只有报表中心有回显&#xff1a; 发现url中id等于1&#xff0c;sql注入尝试无果&#xff0c; burp工具爆破id 对id的值进行爆破 burp报ERROR的话这是个bug&#xff0c;先点击Hex后点decimal手动刷新就可以使用 强行总…

crontab用法与实例

crontab用法与实例 本文基于 ubuntu 18.04 在Linux系统的实际使用中&#xff0c;可能会经常碰到让系统在某个特定时间执行某些任务的情况&#xff0c;比如定时采集服务器的状态信息、负载状况&#xff1b;定时执行某些任务/脚本来对远端进行数据采集等。这里将介绍下crontab的配…

手工sql注入常规总结

1.发现注入点 2.报数据库 先用单引号&#xff08;也尝试双引号&#xff09;闭合前面的语句&#xff0c;使注入的语句能够执行&#xff0c; 数字 0 :匹配字段&#xff0c;还有 11 12 等等都可以使用&#xff0c;有些网站会有过滤处理&#xff0c;建议采用 1%2b12 1%2b1>1 绕…

Systemd入门教程:命令篇

Systemd入门教程&#xff1a;命令篇 转自&#xff1a;http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html 作者&#xff1a; 阮一峰 日期&#xff1a; 2016年3月 7日 Systemd 是 Linux 系统工具&#xff0c;用来启动守护进程&#xff0c;已成为大多数…

【CVE-2018-12613】phpmyadmin 4.8.1 远程文件包含漏洞复现

**环境&#xff1a;**http://62.234.56.138:8080/server_databases.php 官网下载phpmyadmin 4.8.1 源码&#xff1a;index.php文件中 函数含义&#xff1a; targer非空targer是否位字符串不能以index为开头&#xff0c;即过滤了index值不能出现在blacklist内&#xff0c;即…

Systemd 入门教程:实战篇

Systemd 入门教程&#xff1a;实战篇 转自&#xff1a;https://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-part-two.html 作者&#xff1a; 阮一峰 日期&#xff1a; 2016年3月 8日 上一篇文章&#xff0c;我介绍了 Systemd 的主要命令&#xff0c;今天介绍如何使…

关于ubuntu自定义service服务时找不到/usr/lib/systemd/system目录的问题

关于ubuntu自定义service服务时找不到/usr/lib/systemd/system目录的问题 问题 我们知道在 systemd 取代了 init 而成为广大 Linux 系统中 PID 为1的守护进程之后&#xff0c;Linux 中的服务&#xff08;service&#xff09;主要有 systemd 命令组来实现。在大多数发行版 Lin…

攻防世界web2(逆向加密算法)

打开网页有如下代码&#xff1a; <?php $miwen"a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";function encode($str){$_ostrrev($str);// echo $_o;for($_00;$_0<strlen($_o);$_0){$_csubstr($_o,$_0,1);$__ord($_c)1;$_cchr($__);$_$_.$…

ctags 基本使用方法

ctags 基本使用方法 简介 ctags&#xff08;Generate tag files for source code&#xff09;是vim下方便代码阅读的工具。尽管ctags也可以支持其它编辑器&#xff0c;但是它正式支持的只有 Vim。并且 Vim 中已经默认安装了 ctags&#xff0c;它可以帮助程序员很容易地浏览源…