Python 装饰器详解(上)

Python 装饰器详解(上)

转自:https://blog.csdn.net/qq_27825451/article/details/84396970,博主仅对其中 demo 实现中不适合python3 版本的语法进行修改,并微调了排版,本转载博客全部例程博主均已亲测可行。

Python 3.8.5

ubuntu 18.04

一、先从一种情况看起

1. 装饰器decorator的由来

装饰器的定义很是抽象,我们来看一个小例子。先定义一个简单的函数:

def myfunc():print('我是函数myfunc')myfunc()  #调用函数

然后呢,我想看看这个函数执行这个函数用了多长时间,好吧,那么我们可以这样做:

import time
def myfunc():start = time.time()print('我是函数myfunc')end = time.time()print(f'函数所花费的时间为 :{end - start}')myfunc()  #函数调用

我们现在已经达到了我们的目的。但是如果是我们还想继续给另外的一些函数也实现同样的功能。那我们是不是给每个函数都添加这么几句话呢?当然可以,但是不高效,而且很麻烦。如果有某一种方式可以一次性解决所有的问题,那自然最好不过了,于是“装饰器”就应运而生。

在上面的例子中,函数本身的功能只是打印一句话而已,但是经过改造后的函数不仅要能够打印这一句话,还要能够显示函数执行所花费的时间,这相当于我要给这个函数添加额外的功能,注意这个关键字,其实“装饰器”就是专门给函数添加额外的功能的

2. 添加额外功能的简单实现——非“装饰器”实现

还记得吗,函数在Python中是一等公民,那么我们可以考虑重新定义一个函数timeit,将myfunc的引用传递给他,然后在timeit中调用myfunc并进行计时,这样,我们就达到了不改动myfunc定义但是又添加了额外功能的目的,代码如下:

import timedef myfunc():print("我是函数myfunc")def timeit(function):start = time.time()function()end =time.time()print(f'函数执行所花费的时间为:{end-start}')timeit(myfunc)

运行结果为:

我是函数myfunc
函数执行所花费的时间为:1.9311904907226562e-05

上面的代码看起来逻辑上并没有问题,也达到了我们所要实现的目的!但是,我们虽然没有修改函数myfunc定义中的代码,但是我们似乎修改了调用部分的代码。原本我们是这样调用的:myfunc(),修改以后变成了:timeit(myfunc)。这样的话,如果myfunc在N处都被调用了,你就不得不去修改这N处的代码。或者更极端的,考虑其中某处调用的代码无法修改这个情况,比如:这个函数是你交给别人使用的。

其实将函数作为参数传递,已经具备了装饰器的雏形了,但是上面的实现还不够好,下面会给出更好地实现方式。

二、什么是装饰器decorator

一般而言,如果我需要给函数添加额外的某一些功能,我需要修改函数的源代码,但是如前面所说,这样麻烦,而且不高效,装饰器就是专门的解决方案!

1. 什么是装饰器?——两个层面

在Python里面有两层定义:

第一:从设计模式的层面上

装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的应用有插入日志、增加计时逻辑来检测性能、加入事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能

第二:从Python的语法层面上(其实第二种本质上也是第一种,只不过在语法上进行了规范化)

简言之,python装饰器就是用于拓展原来函数功能的一种函数,这个函数的特殊之处在于它的返回值也是一个函数,使用python装饰器的好处就是在不用更改原函数的代码前提下给函数增加新的功能。 如此一来,我们要想拓展原来函数代码,就不需要再在函数里面修改源代码了。

2. 装饰器的作用——两方面

(1)抽离雷同代码,加以重用

(2)为函数添加额外的功能

3. 装饰器的使用场景

(1)缓存装饰器

(2)权限验证装饰器

(3)计时装饰器

(4)日志装饰器

(5)路由装饰器

(6)异常处理装饰器

(7)错误重试装饰器

三、装饰器的实现

1. 装饰器的逐步实现

针对上面改进版的代码所存在的哪些问题,我们想出了解决办法:

既然修改N处的调用代码很麻烦,我们就来想想办法不修改调用代码;如果不修改调用代码,也就意味着调用myfunc()需要产生调用timeit(myfunc)的效果。

因为python中一切皆对象,故而我们可以想到将timeit赋值给myfunc

代码如下:

import timedef myfunc():print("我是函数myfunc")def timeit(function):start = time.time()function()end =time.time()print(f'函数执行所花费的时间为:{end-start}')myfunc=timeit  #将timeit赋值给原来的myfunc
myfunc()

但是上面的调用并不会成功,会显示出如下错误:

TypeError: timeit() missing 1 required positional argument: 'function'

这是因为将timeit赋值给myfunc之后,此时myfunctimeit表示的是同一个东西,但是timeit似乎带有一个参数function,而在调用myfunc()的时候并没有传入任何参数,所以并不会成功。

但是上面的调用虽然没有成功,却给我们指出了一条重要的线索,因为上面的代码已经解决“修改调用代码”的问题,只不过是参数没有统一而已,那就想办法把参数统一吧!那就再添加一个函数呗!什么意思?

因为参数不统一,如果timeit()并不是直接添加额外的功能,而是返回一个与myfunc参数列表一致的函数。而原来timeit需要添加额外功能的代码再在timeit里面定义一个函数,由它去完成不就可以了吗,将timeit(myfunc)的返回值赋值给myfunc,然后,调用myfunc()的代码完全不用修改。——即我们依然是调用myfunc(调用代码没变),但是同样却达到了添加额外功能的效果!

代码如下:

import time
#原来的函数myfunc
def myfunc():print("我是函数myfunc")#定义一个计时器
def timeit(function):'''timeit函数负责返回一个wrapper,wrapper的参数要与原来的myfunc保持相同这样一来,执行 myfunc=timeit(myfunc)  myfunc完全等价于wrapperwrapper函数负责添加额外功能'''def wrapper():start = time.time()function()end =time.time()print(f'函数执行所花费的时间为:{end-start}')return wrappermyfunc=timeit(myfunc)  #注意,这里与前面的 “myfunc=timeit”是有所区别的哦
myfunc()  #还和原来调用myfunc()一样,但是达到了添加额外功能的效果

执行结果:

我是函数myfunc
函数执行所花费的时间为:1.8596649169921875e-05

总结:在上面的函数定义和调用中,看起来我的调用myfunc()和原来并没有任何不同,但是却已经添加了额外的效果。它解决前面存在的两个问题

(1)不用修改函数源代码,也不用修改调用函数的代码,完全跟调用最原始的myfunc()代码一样,但是却添加了额外功能;

(2)解决了timeit和myfunc的参数不统一问题,那就是再添加一层wrapper;

——这就是装饰器。

上面的装饰器就是最原始的版本,但是python中引入了专门的“语法糖”来实现装饰器,这样看起来更加专业,更加美观。就是使用字符 @ 去实现。代码如下:

import time#定义一个计时器
def timeit(function):'''timeit函数负责返回一个wrapper,wrapper的参数要与原来的myfunc保持相同这样一来,执行 myfunc=timeit(myfunc)  myfunc完全等价于wrapperwrapper函数负责添加额外功能'''def wrapper():start = time.time()function()end =time.time()print(f'函数执行所花费的时间为:{end-start}')return wrapper#myfunc=timeit(myfunc)  #注意,这里与前面的 “myfunc=timeit”是有所区别的哦#原来的函数myfunc
@timeit
def myfunc():print("我是函数myfunc")myfunc()  #还和原来调用myfunc()一样,但是达到了添加额外功能的效果

输出结果同样是:

我是函数myfunc
函数执行所花费的时间为:1.7881393432617188e-05

在上面的例子中,在定义myfunc函数的上面加了一个 @timeit,这与前面的写法 myfunc = timeit(myfunc) 完全等价,

@有两个重要的作用,第一:较少了代码书写量;第二:那就是让我们的代码看上去更有装饰器的感觉,看起来更高端了。

总结

在这个例子中,函数进入和退出时需要计时,这被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。与传统编程习惯的从上往下执行方式相比较而言,像是在函数执行的流程中横向地插入了一段逻辑。在特定的业务领域里,能减少大量重复代码。面向切面编程还有相当多的术语,这里就不多做介绍,感兴趣的话可以去找找相关的资料(如果有需要,我后面也会抽时间专门写一系列关于面向切面编程的文章,看我有没有时间啦!)

2. 装饰器的一般结构

为了能够明确装饰器的实现原理,这里给出一个关于装饰器的 “一般模板” ,方便大家理解!但是,装饰器作为一种设计模式,本身是没有固定的设计模板的,语法也是相对较为灵活,没有说一定要怎么写才正确

模板如下:

def decorator(function):'''第一层函数为装饰器名称function:参数,即需要装饰的函数return:返回值wrapper,为了保持与原函数参数一致'''def wrapper(*arg,**args):'''内层函数,这个函数实现“添加额外功能”的任务*arg,**args:参数保持与需要装饰的函数参数一致,这里用*arg和**args代替'''#这里就是额外功能代码function()   #执行原函数#这里就是额外功能代码return wrapper

一般就按照上面这个模板写“装饰器”函数,一般就不会出错了。

四、装饰器的各种花式实现

学过装饰器的人都知道Python的闭包,关于“闭包”的详细定义有各种版本,但我们经常看见这样一句话,“Python的装饰器就是一种闭包或者是Python的闭包其实就是装饰器”,这句话在一定程度上是不正确的,但是这么说也可以(心里要明白二者的本质)。

本质:python闭包是装饰器的真子集,即装饰器是更加宽泛的概念,至于为什么,它们二者的区别和联系,我会在

Python高级编程——装饰器Decorator详解(上篇)

中继续讲解python闭包和装饰器的区别和联系。

不仅如此,上面所实现的装饰器是针对函数的,实际上Python的装饰器可以是“函数”或者是“类”,而被装饰的对象也可以是“函数”或者是“类”,这样一来,就有四种搭配情况,即:

  • 函数装饰函数

  • 函数装饰类

  • 类装饰函数

  • 类装饰类

具体每一种怎么实现呢?其实他们的设计思想都是大同小异,只是实现细节略有不同,欲知详细情况,且听下回分解!!!

下一篇预告:

装饰器与闭包的联系和区别

四大类装饰器的搭配实现

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

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

相关文章

xss原理和注入类型

XSS漏洞原理 : XSS又叫CSS(cross Site Script), 跨站脚本攻击,指的是恶意攻击者往Web页面里插入恶意JS代码,当用户浏览该页时,嵌入其中的Web里的JS代码就会被执行,从而达到恶意的特殊目的. 比如:拿到cooike XSS漏洞分类: 反射性(非存储型) payload没有经过存储,后端接收后,直接…

Python 装饰器详解(中)

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

存储型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…