python高级语法装饰器_Python高级编程——装饰器Decorator超详细讲解上

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

送你小心心记得关注我哦!!

进入正文

全文摘要

装饰器decorator,是python语言的重要特性,我们平时都会遇到,无论是面向对象的设计或者是使用相关的库,但是很少有文章对装饰器的来龙去脉进行深入详解,导致的结果就是很多人知其然,不知其所以然,所以我打算出一期关于Python装饰器的详解文章,由于内容较多,该文章共分为上下两篇,本文讲解上篇,下篇会在后面发出,请记得持续关注哦!文章偏长,阅读全文约20min。

全文目录

01 python装饰器诞生的背景

1.1 从一种简单的情况说起

1.2 非“装饰器”实现添加额外功能

02 什么是装饰器decorator

2.1 装饰器的两个层面的定义

2.2 装饰起的作用——两个方面

2.3 装饰器的使用场景

03 装饰器的实现

3.1 装饰器的逐步实现

3.2 装饰器的一般“模板”

04 装饰器的分类实现(下篇预告)

python装饰器详解(上篇)

01

python装饰器诞生的背景

01 decorator诞生的背景

1.1 从一种简单的情况说起

装饰器的定义很是抽象,各个不同的文章有不同的表述,但是我不打算一开始就告诉你什么是Python的装饰器,这样显太过突兀,我们来看一个小例子。

先定义一个简单的函数:

def myfunc:

print('我是函数myfunc')

myfunc() #调用函数

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

import time

def myfunc:

start = time.clock()

print('我是函数myfunc')

end = time.clock()

print(f'函数所花费的时间为 :{end - start}')

myfunc() #函数调用

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

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

01 decorator诞生的背景

1.2 非“装饰器”实现添加额外功能

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

import time

def myfunc():

print("我是函数myfunc")

def timeit(function):

start = time.clock()

function()

end =time.clock()

print(f'函数执行所花费的时间为:{end-start}')

timeit(myfunc)

运行结果为:

我是函数myfunc

函数执行所花费的时间为:0.0004924657368762765

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

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

02

什么是装饰器-decorator

02 什么是装饰器decorator

2.1 装饰器的定义——两个层面

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

装饰器的定义主要从两个不同层面进行说明的。

在Python里面有两层定义:

第一:从设计模式的层面上——代码重用

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

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

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

02 什么是装饰器decorator

2.2 装饰器的作用——两个层面

通过上面的表述,得出两个最基本的结论,其实这也是装饰器两个最基本的作用:

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

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

02 什么是装饰器decorator

2.3 装饰器的使用场景

装饰器可能在我们平时的编码中比较少去自己定义,更多的是我们使用别人的已经编写好的装饰器,比如我们我们经常使用的@staticmethod,@classmethod,@property等等,都是别人写好了,我们自己不需要自己再实现了,在编码中,我们在下面的一些情况会经常遇见装饰器。

(1)缓存装饰器

(2)权限验证装饰器

(3)计时装饰器

(4)日志装饰器

(5)路由装饰器

(6)异常处理装饰器

(7)错误重试装饰器

后面我还会讲到关于python的高级语法——python描述符(descriptor),其实也是跟python的装饰器有着千丝万缕的关系,详细可以参见后面的文章哦!

03

装饰器decorator的实现

03 装饰器decorator的实现

3.1 装饰器的逐步实现

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

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

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

import time

def myfunc():

print("我是函数myfunc")

def timeit(function):

start = time.clock()

function()

end =time.clock()

print(f'函数执行所花费的时间为:{end-start}')

myfunc=timeit #将timeit赋值给原来的myfunc

myfunc()

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

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

为什么呢?这是因为将timeit赋值给myfunc之后,此时myfunc和timeit表示的同一个东西,但是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完全等价于wrapper

wrapper函数负责添加额外功能

'''

def wrapper():

start = time.clock()

function()

end =time.clock()

print(f'函数执行所花费的时间为:{end-start}')

return wrapper

myfunc=timeit(myfunc) #注意,这里与前面的 “myfunc=timeit”是有所区别的哦

myfunc() #还和原来调用myfunc()一样,但是达到了添加额外功能的效果

上面的运行结果就出来了,如下:

我是函数myfunc

函数执行所花费的时间为:0.0005973331798019136

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

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

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

——这就是装饰器。

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

import time

#定义一个计时器

def timeit(function):

'''

timeit函数负责返回一个wrapper,wrapper的参数要与原来的myfunc保持相同

这样一来,执行 myfunc=timeit(myfunc) myfunc完全等价于wrapper

wrapper函数负责添加额外功能

'''

def wrapper():

start = time.clock()

function()

end =time.clock()

print(f'函数执行所花费的时间为:{end-start}')

return wrapper

#myfunc=timeit(myfunc) #注意,这里与前面的 “myfunc=timeit”是有所区别的哦

#原来的函数myfunc

@timeit

def myfunc():

print("我是函数myfunc")

myfunc() #还和原来调用myfunc()一样,但是达到了添加额外功能的效果

上面代码的运行结果依然是:

我是函数myfunc

函数执行所花费的时间为:0.0004893814003196401

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

@有两个重要的作用,

第一:较少了代码书写量;

第二:那就是让我们的代码看上去更有装饰器的感觉,看起来更高端了。

总结

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

03 装饰器decorator的实现

3.2 装饰器的一般“模板”

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

模板如下:

def decorator(function):

'''

第一层函数为装饰器名称

function:参数,即需要装饰的函数

return:返回值wrapper,为了保持与原函数参数一致

'''

def wrapper(*arg,**args):

'''

内层函数,这个函数实现“添加额外功能”的任务

*arg,**args:参数保持与需要装饰的函数参数一致,这里用*arg和**args代替

'''

#这里就是额外功能代码

function() #执行原函数

#这里就是额外功能代码

return wrapper

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

注意事项:

(1)装饰器一般由两层函数组成,外层的decorator,和内层的wrapper;

(2)第一层函数的参数function,即需要装饰的函数,返回值wrapper,为了保持与原函数参数一致

(3)内层函数,这个函数实现“添加额外功能”的任务, *arg,**args:参数保持与需要装饰的函数参数一致,这里用*arg和**args代替。

04

装饰器的各种花式实现

04 装饰器的各种花式实现

4.1 装饰器的分类型实现

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

本质:python闭包是装饰器的真子集,即装饰器是更加宽泛的概念,至于为什么,它们二者的区别和联系,我会在下一篇文章里面详细说明。下一篇参考:

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

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

函数装饰函数

函数装饰类

类装饰函数

类装饰类

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

下一篇预告:

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

四大类装饰器的搭配实现

····

本文后面分享三本python使用的电子书,分别是获取方式:关注转发文章私信小编(学习)就可以了

《python面试宝典》《python核心编程第三版》《利用python进行数据分析第二版》

获取方式:关注转发文章私信小编(学习)就可以了

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

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

相关文章

深入理解CPU和异构计算芯片GPU/FPGA/ASIC (上篇)

王玉伟,腾讯TEG架构平台部平台开发中心基础研发组资深工程师,专注于为数据中心提供高效的异构加速云解决方案。目前,FPGA已在腾讯海量图片处理以及检测领域已规模上线。 随着互联网用户的快速增长,数据体量的急剧膨胀,…

jenkins-基础配置

一,配置远程连接服务器 系统管理 --> 系统设置 SSH remote hosts 二,设置docke的URL(设置jenkins构建镜像时候所连接的docker url ,参考 docker开启远程访问https://www.cnblogs.com/galsnag/articles/10069709.html&#xf…

JSF:直接从页面将参数传递给JSF操作方法,这是JavaEE 6+的一个不错的功能

Java企业版JavaEE 6中提供的JSF 2的一项不错的功能是,您可以将参数传递给任何操作组件(例如commandButton或commandLink组件)的操作方法。 基于此,您可以最大程度地减少托管bean中的方法数量。 另外,为了最小化在bea…

CCF CSP个人题解汇总

趁着这波考CCF热来骗一波访问量 祝自己免修算法RP 区域赛RP 1、2题汇总在这:https://www.cnblogs.com/QAQorz/p/9650890.html 201803-4 棋局评估(对抗搜索):https://www.cnblogs.com/QAQorz/p/9650828.html 201709-4 通信网络&…

海洋主题绘画_深圳举办风帆时代海洋绘画作品展,展出作品600余件

12月12日,第七届《风帆时代海洋绘画作品展》在位于蛇口邮轮中心3楼的深圳大学海洋文化科普教育基地举行开幕仪式。该项目得到深圳市宣传文化事业专项基金支持,由深圳大学海洋艺术研究中心主办,深圳市海洋文化艺术研究会承办。作为开幕式重要环…

不要被约束的意思_不要再奢望你会变得自律了丨“他律”比“自律”更重要

高三寒假和同学打赌一个假期做完400套卷子。否则给他1000元。。。然后每天早上六点晚上12点,春节也没过,最后做完了卷子,我也完成了自己的梦想!!!然而上面这个大神不是我,是我引用的一颗真实栗子…

一篇文章为你深度解析HTTPS 协议

一、前言 微信小程序如期发布,开发者在接入微信小程序过程中,会遇到以下问题: 小程序要求必须通过 HTTPS 完成与服务端通信,若开发者选择自行搭建 HTTPS 服务,那需要自行 SSL 证书申请、部署,完成 https …

Shadow DOM及自定义标签

参考链接:点我 一、什么是Shadow DOM Shadow DOM,直接翻译的话就是 影子 DOM,可以理解为潜藏在 DOM 结构中并且我们无法直接控制操纵的 DOM 结构。类似于下面这种结构 Shadow DOM 可以在浏览器中生成一个独立于DOM树之外的 DOM结构 二、Shado…

二进制逆向工程师_利用Ghidra逆向分析Go二进制程序(下篇)

(接上文)动态分配字符串结构在第一种情况下,字符串结构是在运行时创建的,为此,需要使用一系列汇编指令在字符串操作之前设置相应的结构。由于指令集的不同,不同的架构之间的结构也是不同的。让我们通过几个案例,来展示…

工艺路线和工序有差别吗_你知道吗?市政道路排水工程的主要工序施工工艺是什么...

易筑教育给排水课程火热招生中!张老师微信号:yizhujiaoyu999市政道排工程施工遵循的基本顺序是:先地下,后地上;先深后浅。按照这个顺序,正常的施工顺序为基础处理、排水管道(涵)施工(雨、污水)、道路基层(常…

如何:从Spring 4.0快速入门以构建简单的REST-Like API(演练)

如何:从Spring 4.0快速入门以构建简单的REST-Like API(演练) 关于使用Spring MVC创建Web API的另一篇教程。 不太复杂。 只是一个演练。 生成的应用程序将提供简单的API,将Mongo作为其持久性,并将通过Spring Security进…

01-Web客户端与服务器详解

1、CS与BS 软件使用方式上两种划分  C/S架构 Client/ServerPC客户端、服务器架构 特点:   在服务器当中就主要是一个数据库,把所有的业务逻辑以及界面都交给客户端完成 优点:   较为安全,用户界面丰富,用户体验好…

java之Hibenate中监听事件的重写和二级cache缓存

管理缓存和统计缓存 Cache cache sessionFactory.getCache(); //清除指定的News对象 cache.evictEntity(News.class, id); //清除所有的news对象 cache.evictEntityRegion(News.class); //清除指定id的news所关联的参与者集合属性 cache.evictColleciton("News.actors&q…

axi ps读写pl_PL读写DDR:Datamover能干什么

最近发现工程项目中一直在用AXI-DMA。这玩意儿搬数据倒是没问题,就是用axi-lite配置起来非常反人类。。。简单的办法其实是用datamover ip核。这个ip核能干嘛呢。准备写个文章解析一下。由于好多feature没用过,所以仅仅看文档可能理解有误,欢…

在10分钟内在新Mac中设置Java开发环境(更新)

这只是一个小的更新文章,它引用了2个较旧的条目( a , b ),我将它们合并为一个步骤,就像一步操作,并确保所有功能都在最新的MacOSX 10.9 Mavericks下工作 。 我主要针对的是初次尝试设置其环境的…

linux path 与 classpath 区别

linux path 与 classpath 区别 一、OS依据path中的路径信息来寻找可执行指令; 例如: cat /etc/profile 我们就可以在任意目录执行hadoop / hdfs / yarn / java 等相关命令了 export HADOOP_HOME/opt/hadoop/hadoop-2.6.0 export JAVA_HOME/home/jdk1.8.0…

开启9008端口进入深刷模式

除了前文所述,使用深刷线,还可以用命令开启9008端口,进入深刷模式。 adb reboot edl fastboot oem edl 这个在小米4c上测试ok 下面这个可能用于其他手机。 fastboot reboot emergency http://www.znsjw.net/nd.jsp?id19 小米绕BL锁9008工程…

Vue Webpack常见问题(持续更新)

常识 1.computed计算属性,使用的属性必需在data里面声明。 computed: {canLogin: function(){//注意这里的依赖的属性必需在data里面声明return this.name && this.password;} } Webpack问题 1.模块里面使用JSON.stringify和 typeof,报&#x…

hashmap为什么用红黑树_要看HashMap源码,先来看看它的设计思想

HashMap 是日常开发中,用的最多的集合类之一,也是面试中经常被问到的 Java 类之一。同时,HashMap 在实现方式上面又有十分典型的范例。不管是从哪一方面来看,学习 HashMap 都可以说是有利无害的。分析 HashMap 的源码的文章在网上…

Hibernate 4.2.8,javassist 3.18.1和ClassCastExceptions –注意您的类路径

我写这篇文章是作为提示和警告,而不是绝对的解决方案。 我将尝试针对我的案例(WebSphere 8.5.5)返回一种解决方法,但是我确信其他开发人员和应用程序也会受到影响。 我已经花了一些时间来找出问题的原因,所以暂时&…