一篇文章带你从认识Python装饰器到熟练使用
一.简单装饰器
装饰器其实就是一个以函数作为参数并返回一个替换函数的可执行函数。本质上就是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值也是一个函数对象,它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等应用场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
从一个例子说起:
业务生产中大量调用的函数::
def foo():print('hello foo')
foo()
现在有一个新的需求,希望可以记录下函数的执行时间,于是在代码中添加日志代码:
import time
def foo():start_time=time.time()print('hello foo')time.sleep(3)end_time=time.time()print('spend %s'%(end_time-start_time))foo()
bar()、bar2()也有类似的需求,怎么做?再在bar函数里调用时间函数?这样就造成大量雷同的代码,为了减少重复写代码,我们可以这样做,重新定义一个函数:专门设定时间:
import time
def show_time(func):start_time=time.time()func()end_time=time.time()print('spend %s'%(end_time-start_time))def foo():print('hello foo')time.sleep(3)show_time(foo)
逻辑上不难理解,而且运行正常。 但是这样的话,你基础平台的函数修改了名字,容易被业务线的人投诉的,因为我们每次都要将一个函数作为参数传递给show_time
函数。而且这种方式已经破坏了原有的代码逻辑结构,之前执行业务逻辑时,执行运行foo(),但是现在不得不改成show_time(foo)
。那么有没有更好的方式的呢?当然有,答案就是装饰器。
if foo()==show_time(foo) :问题解决!
所以,我们需要show_time(foo)
返回一个函数对象,而这个函数对象内则是核心业务函数:执行func()与装饰函数时间计算,修改如下:
import timedef show_time(func):def wrapper():start_time=time.time()func()end_time=time.time()print('spend %s'%(end_time-start_time))return wrapperdef foo():print('hello foo')time.sleep(3)foo=show_time(foo)
foo()
函数show_time就是装饰器,它把真正的业务方法func包裹在函数里面,看起来像foo被上下时间函数装饰了。在这个例子中,函数进入和退出时 ,被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。
@符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作
import timedef show_time(func):def wrapper():start_time=time.time()func()end_time=time.time()print('spend %s'%(end_time-start_time))return wrapper@show_time #foo=show_time(foo)
def foo():print('hello foo')time.sleep(3)@show_time #bar=show_time(bar)
def bar():print('in the bar')time.sleep(2)foo()
print('***********')
bar()
如上所示,这样我们就可以省去bar = show_time(bar)
这一句了,直接调用bar()即可得到想要的结果。如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性。
这里需要注意的问题:
foo=show_time(foo)
其实是把wrapper引用的对象引用给了foo,而wrapper里的变量func之所以可以用,就是因为wrapper是一个闭包函数。
二.带参数的被装饰函数
import timedef show_time(func):def wrapper(a,b):start_time=time.time()func(a,b)end_time=time.time()print('spend %s'%(end_time-start_time))return wrapper@show_time #add=show_time(add)
def add(a,b):time.sleep(1)print(a+b)add(2,4)
import timedef show_time(func):def wrapper(a,b):start_time=time.time()ret=func(a,b)end_time=time.time()print('spend %s'%(end_time-start_time))return retreturn wrapper@show_time #add=show_time(add)
def add(a,b):time.sleep(1)return a+bprint(add(2,5))
不定长参数
import timedef show_time(func):def wrapper(*args,**kwargs):start_time=time.time()func(*args,**kwargs)end_time=time.time()print('spend %s'%(end_time-start_time))return wrapper@show_time #add=show_time(add)
def add(*args,**kwargs):time.sleep(1)sum=0for i in args:sum+=iprint(sum)add(2,4,8,9)
三.带参数的装饰器
装饰器还有更大的灵活性,例如带参数的装饰器:在上面的装饰器调用中,比如@show_time
,该装饰器唯一的参数就是执行业务的函数。装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。
import timedef time_logger(flag=0):def show_time(func):def wrapper(*args,**kwargs):start_time=time.time()func(*args,**kwargs)end_time=time.time()print('spend %s'%(end_time-start_time))if flag:print('将这个操作的时间记录到日志中')return wrapperreturn show_time@time_logger(3)
def add(*args,**kwargs):time.sleep(1)sum=0for i in args:sum+=iprint(sum)add(2,7,5)
@time_logger(3)
做了两件事:
(1)ime_logger(3)
:得到闭包函数show_time
,里面保存环境变量flag
(2)@show_time
:add=show_time(add)
上面的time_logger
是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器(一个含有参数的闭包函数)。当我 们使用@time_logger(3)
调用的时候,Python能够发现这一层的封装,并把参数传递到装饰器的环境中。
四,多层装饰器
def makebold(fn):def wrapper():return "<b>" + fn() + "</b>"return wrapperdef makeitalic(fn):def wrapper():return "<i>" + fn() + "</i>"return wrapper@makebold
@makeitalic
def hello():return "hello alvin"hello()
过程:
五.类装饰器
再来看看类装饰器,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器还可以依靠类内部的__call__
方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。
'''
学习中遇到问题没人解答?小编创建了一个Python学习交流QQ群:725638078
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
import timeclass Foo(object):def __init__(self, func):self._func = funcdef __call__(self):start_time=time.time()self._func()end_time=time.time()print('spend %s'%(end_time-start_time))@Foo #bar=Foo(bar)def bar():print ('bar')time.sleep(2)bar() #bar=Foo(bar)()>>>>>>>没有嵌套关系了,直接active Foo的 __call__方法
六.functools.wraps
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__
、参数列表,先看例子:
def foo():print("hello foo")print(foo.__name__)
#####################def logged(func):def wrapper(*args, **kwargs):print (func.__name__ + " was called")return func(*args, **kwargs)return wrapper@logged
def cal(x):return x + x * xprint(cal.__name__)########
# foo
# wrapper
解释:
@logged
def f(x):return x + x * x
等价于:
def f(x):return x + x * x
f = logged(f)
不难发现,函数f被wrapper取代了,当然它的docstring,__name__
就是变成了wrapper函数的信息了。
print (f.__name__ ) # prints 'wrapper'
print (f.__doc__ ) # prints None
这个问题就比较严重的,好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。
from functools import wrapsdef logged(func):@wraps(func)def wrapper(*args, **kwargs):print (func.__name__ + " was called")return func(*args, **kwargs)return wrapper@logged
def cal(x):return x + x * xprint(cal.__name__) #cal
结尾给大家推荐一个非常好的学习教程,希望对你学习Python有帮助!
Python基础入门教程推荐:←点击左边蓝色文字就可以跳转观看了
Python爬虫案例教程推荐:←点击左边蓝色文字就可以跳转观看了
一篇文章带你从认识Python装饰器到熟练使用相关推荐
- 一篇文章带你搞懂Python中的类
前言 今天我们要说的是面向对象的核心-----类,类能帮我们把复杂的事情变得有条理,有顺序,希望大家通过学习类能改善自己的编码风格,使代码变得更为好看,更加通俗易懂. 1.类的用法 一.什么是类 类( ...
- 一篇文章带你搞定Python返回函数
一.什么是返回函数? 返回函数,简单的说就是返回值是一个函数. 返回的是函数,调用返回的函数的时候才会返回结果. 二.闭包 (以返回函数的形式实现) 相关参数和变量都保存在返回的函数中,这种称为&qu ...
- Python是什么?一篇文章带你全面了解Python
Python在各种编程语言里是一种怎样的存在?"各种编程语言"这个范围就有点大了. 现在,全世界差不多有600多种编程语言,流行的编程语言也有20来种.但如果你听说过TIOBE排行 ...
- 一篇文章带你读懂Python的魔法方法
作者:Python进阶者 来源:Python爬虫与数据挖掘 今 日 鸡 汤 燕雀安知鸿鹄之志哉? /1 前言/ 魔法方法是python 里面非常重要的一个分支,平时我们写代码或多或少都会接触到他们,而 ...
- python装饰器传参与不传参_Python装饰器不会传参?别着急,这篇文章为你解惑
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是Python专题的第13篇文章,上一篇文章当中我们介绍了Python装饰器的定义和基本的用法,这篇文章我们一起来学习一下Python装 ...
- Python 装饰器详解(下)
Python 装饰器详解(下) 转自:https://blog.csdn.net/qq_27825451/article/details/84627016,博主仅对其中 demo 实现中不适合pyth ...
- Python 装饰器详解(中)
Python 装饰器详解(中) 转自:https://blog.csdn.net/qq_27825451/article/details/84581272,博主仅对其中 demo 实现中不适合pyth ...
- 利用亚运会,读懂 Python装饰器
阅读文本大概需要 5 分钟. 2018 年印度雅加达亚运会已接近尾声,中国在金牌榜和总奖牌榜都遥遥领先于第二名的日本,我也是一名体育爱好者,平时有比赛也会看.看到中国的国旗在海外飘扬,内心会格外的自豪 ...
- 一篇文章汇总Python装饰器全知识图谱(使用场景,基本用法,参数传递,闭包操作,类装饰器和AOP)
装饰器,是将Python代码变得低耦合,简洁优美的必经之路,同时也是实现闭包操作,AOP编程的基础.这一篇博客从装饰器的产生原因,基本使用,延伸到参数传递,闭包操作,最后到类装饰器和AOP,希望能用我 ...
最新文章
- 程序员的自我修养--链接、装载与库笔记:Windows PE/COFF
- SpringBoot+MyBatis登录案例
- 将SVG 图引入到HTML页面
- 那些读了硕士博士的人,最不想让你知道的是什么?
- JAVA 网络编程小记
- 子元素增加margin-top会增加给父元素的问题
- 戚俊:可能是最懂架构的投资人
- arm跑操作系统的意义_不太远的猜想:当ARM和鸿蒙OS在笔记本领域相遇,颠覆已无可避免...
- 面对 Google、Facebook、微软等科技巨头的围剿,夹缝中的初创企业该何去何从?...
- 关于PHP你可能不知道的10件事
- pytorch中的Variable详解
- 解决Maven:com.oracle:ojdbc7-12.1.0.2.jar在pom文件中无法下载问题
- LintCode 171. Anagrams
- 海量数据(面向面试)
- pygame编写井字棋游戏
- UML(thinking in uml 学习)--参与者和业务工人和涉众
- 使用Epub.js打开本地Epub文件
- java开发工程师培训费,南通渡课Java开发工程师培训费用是多少
- 在Matplotlib中将图片导出
- 1. Resnet网络详解
热门文章
- python中 __name__及__main()__的妙处
- 如何制作高水平简历? 制作简历时需要注意的问题
- python subprocess 模块
- Spring控制反转(依赖注入)的最简单说明
- 安装apache服务出错,无法启动此程序,因为计算机中丢失VCRUNTIME140.dll 尝试重新安装此程序以解决此问题...
- [javaSE] 标识符大小写
- Samrty技术的 初步了解
- 使用Git工具下载android源码---带步骤
- 看完你会为自己哭,或者为他们哭
- Vista操作系统评估参考资料