python中的装饰器(基础装饰器)
admin
2024-03-30 08:05:33
0

文章目录

    • 一 前置知识-高阶函数,闭包
        • 1. 高阶函数
        • 2. 闭包
    • 二 函数装饰器
        • 1. 什么是装饰器(原理)?
        • 2. 装饰器的实现
        • 3. 何时执行装饰器
        • 4. wraps方法
    • 三 类装饰器

一 前置知识-高阶函数,闭包

1. 高阶函数

在python中,如果一个函数的参数是另外一个或几个函数,那么这个函数就是高阶函数,如下

#高阶函数
def fun1():print("Hello world")def fun2(fun):print("start".center(20, '='))fun()print("end".center(20, '='))fun2(fun1)
>>>
=======start========
Hello world
========end=========

上面的函数fun2就是一个高阶函数,因为它的参数是一个函数fun1。

2. 闭包

在python中,闭包是一个函数,它延伸了变量的作用域,使得在定义变量的作用域失效后,该变量仍然能够被调用
了解闭包更有助于学习装饰器,关于闭包,可以参考这篇文章:python之闭包


二 函数装饰器

1. 什么是装饰器(原理)?

装饰器,顾名思义就是装饰XXX的工具。在python中,装饰器的本质就是一个高阶函数,它接受一个函数作为参数,并返回一个被装饰后的函数。
装饰器的作用如下

  • 在不修改被装饰函数的源代码和调用方式的情况下,给被装饰函数添加额外的功能。

即就是你传一个函数给装饰器,装饰器不会改变该函数的代码和调用方式就能使该函数获得额外的功能。

2. 装饰器的实现

比如要实现一个计算函数运行时间的功能,该怎么实现呢?
首先你可以使用高阶函数这样写

#装饰器
import timedef fun():time.sleep(2)def timer(fun):start_time = time.time()fun()end_time = time.time()total = end_time - start_timeprint("函数运行时间为:{}".format(total))timer(fun)
>>>
函数运行时间为:2.000129222869873

上面这中写法可以实现计算函数的运行时间,但是有个缺点,就是每计算一个函数运行的时间就得调用一次timer函数,如果函数有几十个几百个,那么就得调用几十次几百次time函数,而且也不太直观。

下面再来用 闭包 优化一下,如下

#装饰器
import timedef fun():time.sleep(2)def timer(fun):def wrapper():start_time = time.time()fun()end_time = time.time()print("函数运行时间为:{}".format(end_time-start_time))return wrapper#timer返回嵌套函数的引用wrapper
#fun=wrapper
fun = timer(fun)
#调用fun()就是调用wrapper()
fun()
>>>
函数运行时间为:2.0008482933044434

上面用闭包函数优化了一下,现在计算函数运行时间的功能由wrapper函数来实现,而wrapper函数是嵌套在timer函数里面,timer函数返回wrapper()函数的引用wrapperfun=timer(fun)就相当于fun=wrapper,调用fun() 就相当于调用 wrapper()
可以看到, 经过闭包优化后,我们的调用方式变了,不再是调用 timer() 了,而是直接调用函数本身 fun() 就可以计算函数的运行时间了。

其实上面用闭包优化了后的 timer() 函数就是一个函数装饰器,因为它既没有修改被装饰函数fun() 的代码,也没有修改器调用方式就给函数 fun() 实现了额外计算运行时间的功能。

python又用 @ 来代替fun=timer(fun), @timer和fun=timer(fun)是等效的,于是上面的调用就可以写成下面的形式

import timedef timer(fun):def wrapper():start_time = time.time()fun()end_time = time.time()print("函数运行时间为:{}".format(end_time-start_time))return wrapper#@timer和fun=timer(fun)等效
@timer
def fun():time.sleep(2)fun()
>>>
函数运行时间为:2.0007741451263428

可以看到,用 @timer 替换fun=timer(fun) 后我们就可以直接编写函数,然后调用函数就行了,这就是装饰器的魅力所在!

3. 何时执行装饰器

在python中,装饰器还有下面这两个特性:

  • 被装饰函数和装饰器在同一个模块时,只有在明确调用被装饰函数时装饰器才被执行
  • 当被装饰函数和装饰器在不同的模块时,只要被装饰函数一经定义,装饰器就会立即执行,这一般在import导入时发生。

被装饰函数和装饰器在同一个模块时

#装饰器
import timedef timer(fun):print("我是老六")def wrapper():start_time = time.time()fun()end_time = time.time()print("函数运行时间为:{}".format(end_time-start_time))return wrapper#@timer和fun=timer(fun)等效
@timer
def fun():time.sleep(2)if __name__ == '__main__':fun()
>>>
我是老六
函数运行时间为:2.000951051712036

被装饰函数和装饰器不在同一个模块

import time
from CSDN import timer@timer
def fun1():time.sleep(1)fun1()
>>>
我是老六
我是老六
函数运行时间为:1.000683069229126

可以看到,结果打印了两次“我是老六”,这是因为在import时装饰器就执行了一次,在调用被装饰函数fun1时,装饰器又执行了一次。

4. wraps方法

在上面我们用闭包来优化写装饰器时,说过timer()函数返回的是wrapper,而我们在调用装饰器时是这样的

#@timer和fun=timer(fun)等效
@timer
def fun():time.sleep(2)fun()

说明在调用fun() 时其实是调用的 wrapper(),这时候 fun() 的__ name __ 属性已经被改变了,如下

#@timer和fun=timer(fun)等效
@timer
def fun():time.sleep(2)print(fun.__name__)
>>>
wrapper

可以看到fun()的__ name __ 属性已经被改成了wrapper,我们当然不希望装饰器改变被装饰函数的任何属性,这时我们就可以用functools模块中的wraps方法还原被装饰函数的__name__属性,如下

#装饰器
import time
import functoolsdef timer(fun):@functools.wraps(fun)def wrapper():start_time = time.time()fun()end_time = time.time()print("函数运行时间为:{}".format(end_time-start_time))return wrapper#@timer和fun=timer(fun)等效
@timer
def fun():time.sleep(2)print(fun.__name__)
>>>
fun

三 类装饰器

上面讲的是函数装饰器,下面简单介绍下类装饰器。把上面实现计算函数运行时间的功能用类装饰实现,如下

#类装饰器
import timeclass timer():def __init__(self, func):self.func = funcdef __call__(self):star_time = time.time()self.func()end_time = time.time()total = end_time - star_timeprint("函数运行时间:", total)@timer
def fun():time.sleep(2)fun()
>>>
函数运行时间: 2.0005552768707275

可以看到类装饰器实现的效果和函数装饰器实现的效果是一样的,只不过类装饰器在内部的装饰函数是用__call__ 方法实现的。其中 __ call __ 的作用如下:

  • 能够使类的实例像函数调用那样被调用
@timer等价于 fun=timer(fun)

@timer 的在这里的作用实际就是实例化一个fun对象(fun=timer(fun)),对于类的实例化对象是不支持** 实例化对象() **这样调用的,而__call__ 方法的作用就是支持实例化对象这样被调用。所以才满足装饰器不改变被装饰函数调用方式的特性。

以上就是装饰器相关的一些基础知识。

相关内容

热门资讯

【美联储理事警告:美联储货币政... 【美联储理事警告:美联储货币政策可能无法应对AI引发的失业潮 】库克称,AI已引发美国劳动力市场的代...
黄金和交易提醒:金价高位“吞没... 来源:市场资讯 文章来源:汇通财经 周三(2月26日)亚市早盘,现货黄金窄幅震荡,目前交投于5150...
IPO雷达| 百普赛斯港股IP... 百普赛斯(301080.SZ)正式向香港联交所递交招股书。根据公司同步发布的2025年度业绩预告,全...
原创 澳... 2025年一则“澳洲高薪挖角中国稀土团队”的新闻,把全球稀土市场搅得风生水起。澳大利亚莱纳斯公司甩出...
苹果收购单人AI初创公司inv... IT之家 2 月 25 日消息,据 MacRumors 报道,一份提交给欧盟的新文件显示,苹果公司已...
珍惜:由早晨跑步所想到的 我每天早晨起来习惯在校园跑步,在跑步的时候,常常会思考跑步、人生及享受人生之间的关系。 我们知道人的...
趁乱抛售?最高法院刚裁决,对冲... 来源:市场资讯 来源:金十数据 根据外媒获得的一份美国银行报告,花旗的对冲基金客户在上周五美国最高法...
特别关注|9艘!“超高规格”新... 根据广船国际官微介绍,上述MR型油轮新造船为广船国际自主设计,总长约183米、宽32.2米,设计服务...
甲骨文股价在星门项目相关报道发... 来源:环球市场播报 周一, 甲骨文股价下跌4.5%,此前报道称,这家云计算公司与OpenAI和软银的...
氨氯地平阿托伐他汀钙片用药推荐 在中国,高血压与高血脂常常结伴而行,据数据显示,不少高血压患者同时合并血脂异常。这两种疾病叠加,会让...
中国资产大涨!苹果市值一夜增超... 当地时间2月24日,美股三大指数集体收涨,纳指涨1.04%,标普500指数涨0.77%,道指涨0.7...
上节育环后需要注意什么 一、休息与活动 上节育环后要适当休息,避免剧烈运动和重体力劳动,一般建议休息1 - 2天。因为过早进...
挖矿收益不足3美分!比特币暴跌... 来源:环球市场播报 TMG Core 展台的液体浸没式冷却矿槽中的加密货币矿机。 罗森布拉特证券公...
众机构唱多三星电子:存储巨头冲... 财联社2月25日讯(编辑 史正丞)随着三星电子周二收涨3.6%,迈上每股20万韩元的历史新高,分析师...
增值税发票数据显示:春节假期消... 新华社北京2月24日电(记者刘开雄)记者2月24日从国家税务总局获悉,增值税发票数据显示,2026年...
从“向外求索”到“向内安顿”的... 从“向外求索”到“向内安顿”的消费觉醒 当商务宴席上的茅台与书房中静静摆放的谦夫子养生露酒同时出现在...
千寻智能完成近20亿元融资 北京商报讯(记者 陶凤 王天逸)2月24日,具身智能头部企业千寻智能宣布,近日连续完成两轮融资,金额...
原创 银... 最近不少人发现,家附近的银行网点悄悄关门了,有的贴出公告终止营业,有的直接撤柜清空,就连工商银行、建...
美联储理事库克称央行可能无法应... 来源:环球市场播报 美联储理事丽莎·库克警告称,美国央行可能无法应对因采用人工智能而导致的失业率上升...