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的装饰器可以是“函数”或者是“类”,而被装饰的对象也可以是“函数”或者是“类”,这样一来,就有四种搭配情况,即:

  • 函数装饰函数

  • 函数装饰类

  • 类装饰函数

  • 类装饰类

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

下一篇预告:

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

四大类装饰器的搭配实现

Python 装饰器详解(上)相关推荐

  1. Python 装饰器详解(下)

    Python 装饰器详解(下) 转自:https://blog.csdn.net/qq_27825451/article/details/84627016,博主仅对其中 demo 实现中不适合pyth ...

  2. Python 装饰器详解(中)

    Python 装饰器详解(中) 转自:https://blog.csdn.net/qq_27825451/article/details/84581272,博主仅对其中 demo 实现中不适合pyth ...

  3. Python装饰器详解,详细介绍它的应用场景

    装饰器的应用场景 附加功能 数据的清理或添加: 函数参数类型验证 @require_ints 类似请求前拦截 数据格式转换 将函数返回字典改为 JSON/YAML 类似响应后篡改 为函数提供额外的数据 ...

  4. python装饰器详解51-python装饰器使用实例详解

    这篇文章主要介绍了python装饰器使用实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 python装饰器的作用就是在不想改变原函数代码的情 ...

  5. python装饰器详解-Python装饰器基础概念与用法详解

    本文实例讲述了Python装饰器基础概念与用法.分享给大家供大家参考,具体如下: 装饰器基础 前面快速介绍了装饰器的语法,在这里,我们将深入装饰器内部工作机制,更详细更系统地介绍装饰器的内容,并学习自 ...

  6. python装饰器详解-python装饰器使用实例详解

    这篇文章主要介绍了python装饰器使用实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 python装饰器的作用就是在不想改变原函数代码的情 ...

  7. python装饰器作用-Python装饰器详解

    1.闭包函数 在看装饰器之前,我们先来搞清楚什么是闭包函数.python是一种面向对象的编程语言,在python中一切皆对象,这样就使得变量所拥有的属性,函数也同样拥有. 这样我们就可以理解在函数内创 ...

  8. python类装饰器详解-Python 装饰器详解

    开放封闭原则: 开放对扩展 封闭修改源代码 改变了人家调用方式 装饰器结构 """ 默认结构为三层!!!每层返回下一层内存地址就可以进行执行函数, 传参:语法糖中的传参可 ...

  9. 这是我见过最全面的Python装饰器详解!没有学不会这种说法

    python装饰器 刚刚接触python的装饰器,简直懵逼了,直接不懂什么意思啊有木有,自己都忘了走了多少遍Debug,查了多少遍资料,才有点点开始明白了. 学习python中有什么不懂的地方,小编这 ...

最新文章

  1. 央视首位AI手语主播亮相,动作精确、实时转译,网友:能接住广权的段子么?...
  2. python多线程没用_为什么我无法在python中加入该线程?
  3. Java中intern()方法的作用
  4. rufus中gpt和mrb磁盘_UEFI/BIOS/MBR/GPT启动过程详解与常见系统启动问题
  5. 建网站如何选择好用的网站源码程序
  6. 谷歌/微软/必应web页面免费翻译插件
  7. 山重水复 Thinkpad T61改装记
  8. linux安装Hive(Hive-2.3.7)
  9. 元素周期表Mac动态桌面壁纸
  10. 兄弟7180dn拆机_兄弟DCP-7010拆机图解
  11. 人工智能,机器学习, 深度学习框架图
  12. 不是技术牛人,如何进去自己梦想的公司
  13. 神器——写Markdown来画流程图、时序图
  14. 安卓苹果手机在微信内打开支付宝h5拉起app支付
  15. 在使用npm install时遇到的问题 npm ERR! code ERESOLVE
  16. php写中国象棋,鲨鱼象棋V0.2.2我自己写的象棋软件欢迎测试
  17. NVIDIA安培架构下MIG技术分析
  18. Java开发环境的搭建
  19. 酷我音乐显示服务器怎么办,酷我音乐歌词服务与其他功能的使用方法是什么?...
  20. TQ2416通过nfs挂载根文件系统启动

热门文章

  1. Nacos 集群整合 Nginx 实现反向代理、负载均衡_03
  2. Centos7 安装docker-compose
  3. win10 下安装、配置、启动mysql5.7
  4. Error和Exception(异常)
  5. python特征工程插件_python特征工程
  6. 非计算机专业教学改革,非计算机专业算法分析与设计教学改革论文
  7. java垃圾回收 分代_Java分代垃圾回收策略原理详解
  8. linux查看redis内存,Linux查看redis占用内存的方法
  9. 上课点名app_【APP种草】网瘾少年的自我救赎之最强锁机软件
  10. MATLAB求图片两圆圆心,求助:如何求此图中两圆的圆心距?