python装饰器函数-python函数装饰器

2023-05-16

什么是装饰器

装饰器是一个可调用的对象,其参数是另一个函数(被装饰的函数)。装饰器可能会:

1,处理被装饰的函数,然后把它返回

2,将其替换成另一个函数或者对象

若有个名为decorate的装饰器,则:

@decoratedeftarget():print('running target()')

等价于:

deftarget():print('running target()')

target= decorate(target)

上述两种写法结果一样,函数执行完之后得到的target不一定是原来那个target()函数,而是decorate(target)返回的函数。

确认被装饰的函数会替换成其他函数的一个示例:

defdeco(func):definner():print('running inner()')return inner #函数deco返回inner对象

@deco#使用deco装饰target

deftarget():print('running target()')

target()#调用target,运行inner

print(target) #target时是inner的引用

如下结果,target被替换掉了,它是inner的引用。

running inner().inner at 0x00000253D76B8A60>

严格来说,装饰器只是语法糖。装饰器可以像常规的可调用对象那样调用,其参数是另一个函数。

装饰器两大特性:

1,能把被装饰的函数替换成其他函数(如前所示)

2,加载模块时立即执行

python执行装饰器时机(加载模块时)

registry = [] #保存被装饰的函数的引用

def register(func): #参数是一个函数

print('running register(%s)' % func) #显示被装饰的函数

registry.append(func)return func #返回传入的函数

@registerdeff1():print('running f1()')

@registerdeff2():print('running f2()')deff3():print('running f3()')defmain():print('running main()')print('registry ->', registry)

f1()

f2()

f3()if __name__ == '__main__':

main()

如上,f1()和f2()被装饰,f3()没有被装饰。结果如下:

running register()

running register()

running main()

registry-> [, ]

running f1()

running f2()

running f3()

如上可知,register在模块中其他函数之前运行(两次),先于main函数执行。调用register时,传给它的参数是被装饰的函数,例如

加载模块后,registry中有两个被装饰函数的引用:f1和f2。这两个函数,以及f3只有在main函数调用时才执行。

若将示例命名为registration.py然后使用import registration.py导入模块,则出现:

running register()

running register()

以上可知,装饰器在导入模块时立即执行,而被装饰的函数只有在明确调用时才执行。

变量作用域规则

一段代码:

deff1(a):print(a)print(b)

f1(3) #报错

代码报错,原因很简单,b没有赋值。现在先给b赋值:

b = 6

deff1(a):print(a)print(b)

f1(3)#结果

3

6

b为一个全局变量,正常输出。再加一点料:

b = 6

deff1(a):print(a)print(b)

b= 9f1(3) #报错

b已经赋值过了,为何上述代码会报错呢。print(a)执行了而print(b)没有执行。事实上,python编译函数定义体时,判断b为局部变量,因为函数中给b赋值了,python从尝试本地环境获取b,调用print(b)时发现b没有绑定值,于是报错。

如果在函数中赋值时想让解释器把b当做全局变量,需要使用global声明:

b = 6

deff1(a):globalbprint(a)print(b)

b= 9f1(3)#结果

3

6

闭包

学习装饰器,必须了解闭包。

闭包:指的是延伸了作用域的函数,其中包含函数定义体中引用,但是不在定义体中定义的非全局变量。关键:它能访问定义体之外的非全局变量。

定义一个计算平均数的函数,每次新加一个数,得到历史上所有加入的数的平均值。

defmake_avg():

series=[]defaverage(new_value):

series.append(new_value)

total=sum(series)return total/len(series)returnaverage

avg=make_avg()print(avg(10))print(avg(11))print(avg(12))

结果:

10.0

10.5

11.0

如上,series是make_avg的局部变量,因为那个函数定义体内初始化了series:serise = [ ]。然而,调用avg(10)时,make_avg函数已经返回了,它本身的作用域也不存在了。

在averager函数中,series是自由变量(在本地作用域中绑定的变量)

1305073-20190114215516817-1379292766.png

上图中,averager函数的闭包延伸到那个函数作用域之外,包含series的绑定。

审查编译后的averager:

print(avg.__code__.co_varnames) #打印局部变量

print(avg.__code__.co_freevars) #打印自由变量

print(avg.__closure__) #__colsure__属性,里面各个元素对应一个自由变量的名称

print(avg.__closure__[0].cell_contents) #取第一个自由变量的值

('new_value', 'total')

('series',)

(,)

[10, 11, 12]

综上,闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍然能使用那些绑定。

nonlocal声明

每次都要计算所有历史值的总和然后求平均值,显然效率不高,更好的方法是只保留平均值以及个数,然后求平均值。这样写:

defmake_avg():

count=0

total=0defaverage(new_value):

count+= 1total+=new_valuereturn total/countreturnaverage

avg=make_avg()print(avg(10))

根据变量域作用规则,count和total不是average函数的局部变量,而直接计算就认为它是局部变量,计算时却又没有绑定值,显然时有问题的。(参见:变量作用域规则)

而上一个average函数也使用了未赋值的series,却没有问题?

事实上,这里利用了列表是可变的对象的这一事实。但是数字,字符串,元组等不可变类型,只能读取,不能更新。若重新绑定,会隐式创建同名局部变量。

python3引入的nonlocal声明解决了这个问题。上述代码改为:

defmake_avg():

count=0

total=0defaverage(new_value):

nonlocal count, total

count+= 1total+=new_valuereturn total/countreturnaverage

avg=make_avg()print(avg(10))

一个简单装饰器

输出函数运行时间的装饰器:

importtimedefclock(func):def clocked(*args):

t0=time.perf_counter()

result= func(*args) #获取原函数结果

elapsed = time.perf_counter() - t0 #运行时间

name = func.__name__ #函数名

arg_str = ','.join(repr(arg) for arg in args) #函数参数

print('[%0.8fs] %s(%s) -> %r' %(elapsed, name, arg_str, result))returnresultreturn clocked #返回内部函数,取代被装饰的函数

使用该装饰器:

@clockdefsnooze(seconds):

time.sleep(seconds)

@clockdeffactorial(n):return 1 if n < 2 else n*factorial(n-1)if __name__ == '__main__':print('*' * 40, 'calling snooze(1)')

snooze(1)print('*' * 40, 'calling factorial(6)')print('6!=', factorial(6))

结果:

**************************************** calling snooze(1)

[1.00001869s] snooze(1) ->None**************************************** calling factorial(6)

[0.00000073s] factorial(1) -> 1[0.00001210s] factorial(2) -> 2[0.00002016s] factorial(3) -> 6[0.00002896s] factorial(4) -> 24[0.00003666s] factorial(5) -> 120[0.00004582s] factorial(6) -> 720

6!= 720

在这个示例中,factorial保存的是clocked函数的引用,每次调用factorial(n),执行的都是clocked(n):

1)记录初始时间

2)调用原来的factorial函数,保存结果

3)计算时间

4)格式化并打印收集的数据

5)返回第2)步保存的结果

这是装饰器的典型行为:把被装饰的函数替换成新函数,二者接受相同参数,而且返回被装饰的函数本身该返回的值,同时做一些额外操作。

标准库中的几个装饰器

1.functools.wraps

//保留原函数的属性,保证装饰器不会对被装饰函数造成影响

defdeco(func):

@functools.wraps(func)definner():print('running inner()')return inner #函数deco返回inner对象

@deco#使用deco装饰target

deftarget():print('running target()')print(target)

不加这个装饰器时:

.inner at 0x00000253D76B8A60>

使用@functools.wraps装饰器之后 ->显示的是原本的函数,保留了原函数__name__,__doc__等属性

2.functools.lru_cache

//缓存数据,避免传入相同的参数时的重复计算

使用递归算法生成第n个斐波那契数:

@clock #使用clock装饰器

deffibonacci(n):if n < 2:returnnreturn fibonacci(n-2) + fibonacci(n-1)if __name__ == '__main__':print(fibonacci(6))

结果:

[0.00000037s] fibonacci(0) ->0

[0.00000073s] fibonacci(1) -> 1[0.00004692s] fibonacci(2) -> 1[0.00000000s] fibonacci(1) -> 1[0.00000037s] fibonacci(0)->0

[0.00000037s] fibonacci(1) -> 1[0.00001540s] fibonacci(2) -> 1[0.00003042s] fibonacci(3) -> 2[0.00009237s] fibonacci(4) -> 3[0.00000037s] fibonacci(1) -> 1[0.00000000s] fibonacci(0)->0

[0.00000037s] fibonacci(1) -> 1[0.00001356s] fibonacci(2) -> 1[0.00002749s] fibonacci(3) -> 2[0.00000037s] fibonacci(0)->0

[0.00000037s] fibonacci(1) -> 1[0.00001356s] fibonacci(2) -> 1[0.00000037s] fibonacci(1) -> 1[0.00000037s] fibonacci(0)->0

[0.00000000s] fibonacci(1) -> 1[0.00001430s] fibonacci(2) -> 1[0.00002749s] fibonacci(3) -> 2[0.00005388s] fibonacci(4) -> 3[0.00009421s] fibonacci(5) -> 5[0.00020087s] fibonacci(6) -> 8

8

许多重复的计算导致浪费时间,使用lru_cache改善:

@functools.lru_cache() #lru_cache是参数化装饰器,必须加上() 可看下节 参数化装饰器

@clockdeffibonacci(n):if n < 2:returnnreturn fibonacci(n-2) + fibonacci(n-1)

时间从0.0002s减少到0.00008s

[0.00000037s] fibonacci(0) ->0

[0.00000037s] fibonacci(1) -> 1[0.00005242s] fibonacci(2) -> 1[0.00000073s] fibonacci(3) -> 2[0.00006635s] fibonacci(4) -> 3[0.00000073s] fibonacci(5) -> 5[0.00008138s] fibonacci(6) -> 8

8

lru_cache使用两个可选参数来配置:lru_cache(maxsize=128,typed=False)

maxsize:缓存个数,满了之后会被扔掉(least recently used 扔掉最近最少使用的数据),理论上应设置为2的幂次

typed:设置为True时,不同类型的参数的运算结果会分开保存,例如1和1.0

3.functools.singledispatch

//类似于c++重载,使用singledispatch装饰的普通函数会变为泛函数:根据第一个参数类型以不同方式执行相同操作的一组函数(称之为单分派;而根据多个参数选择专门的函数,称为多分派)

python不支持重载方法或函数,使用if/elif/elif来处理不同类型的数据显得稍显笨拙,不便于扩展。而functools.singledispatch提供了类似于重载的方式,根据传入的不同类型返回结果

from functools importsingledispatch

@singledispatchdefshow(obj):print (obj, type(obj), "obj")

@show.register(str)def_(text):print (text, type(text), "str")

@show.register(int)def_(n):print (n, type(n), "int")

show(1)

show("helloworld")

show([1])

结果:

1 int

helloworldstr

[1] obj

叠放装饰器

@d1

@d2

def f():

xxx

等同于:

def f():

xxx

f= d1(d2(f))

参数化装饰器

python把被装饰的函数作为第一个参数传给装饰器函数。如果要让装饰器接受其他函数,就需要创建一个装饰器工厂函数,把参数传给它,返回一个装饰器,然后再把它应用到要装饰的函数上。

对于clock装饰器,加一点料,让用户传入一个格式字符串,控制被装饰函数的输出:

importtimefrom functools importwraps

DEFAULT_FMT= '[{elapsed:0.8f}s] {name}({arg_str}) -> {_result}'

def clock(fmt=DEFAULT_FMT):defdecorate(func):

@wraps(func)def clocked(*args, **kwargs):

t0=time.perf_counter()

result= func(*args) #获取原函数结果

elapsed = time.perf_counter() - t0 #运行时间

name = func.__name__ #函数名

arg_list =[]ifargs:

arg_list.append(','.join(repr(arg) for arg inargs))ifkwargs:

pairs= ['%s=%r' % (k, w) for k, w insorted(kwargs.items())]

arg_list.append(','.join(pairs))

arg_str= ','.join(arg_list)

_result=repr(result)print(fmt.format(**locals()))returnresultreturnclockedreturndecorateif __name__ == '__main__':

@clock()defsnooze(seconds):

time.sleep(seconds)for i in range(3):

snooze(.123)

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

这个clock装饰器,clock是参数化装饰器工厂函数,decorate是真正的装饰器,clocked包装被装饰的函数;clocked会取代被装饰的函数,返回被装饰的函数原本返回值,decorate返回clocked,clock返回decorete

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

默认输出格式:'[{elapsed:0.8f}s] {name}({arg_str}) -> {_result}'    上述代码输出:

[0.12360011s] snooze(0.123) ->None

[0.12296046s] snooze(0.123) ->None

[0.12395127s] snooze(0.123) -> None

调整格式:

if __name__ == '__main__':

@clock('{name}({arg_str}) dt = {elapsed:0.8f}s')defsnooze(seconds):

time.sleep(seconds)for i in range(3):

snooze(.123)

输出结果

snooze(0.123) dt =0.12316317s

snooze(0.123) dt =0.12387173s

snooze(0.123) dt = 0.12382994s

由于类也是可调用对象,而调用类即调用类的__call__方法,因此类装饰器需要实现__call__方法。事实上,装饰器最好通过实现了__call__方法的类来实现而不是通过普通函数来实现。

以上来自《流畅的python》

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

python装饰器函数-python函数装饰器 的相关文章

  • 使用ORB_SLAM3运行Realsense T265

    关于硬件 官网说明 使用说明 Realsense T265是一款跟踪相机 xff0c 配有两个FOV为111 7 x 108 6的广角相机 xff0c 并且带有IMU BMI055 惯性测量单元 设备内部配有vpu处理器并嵌入了建图和定位算
  • ceres-solver和g2o性能比较

    前言 ceres solver 和 g2o 是slam领域常见的优化器 xff0c 其中ceres solver被vins mono使用 xff0c 而g2o被orb slam3使用 xff0c 因此到底哪个优化器更适合于在slam算法开发
  • FreeRTOS的vTaskDelete使用说明

    FreeRTOS的vTaskDelete使用说明 函数说明 参数 xff1a xTaskToDelete 要删除的任务的任务句柄 返回值 无 说明 删除一个用函数xTaskCreate 或者xTaskCreateStatic 创建的任务 x
  • 机器学习——随机森林(Random Forest)

    1 随机森林 xff08 random forest xff09 简介 随机森林是一种集成算法 xff08 Ensemble Learning xff09 xff0c 它属于Bagging类型 xff0c 通过组合多个弱分类器 xff0c
  • 《基础知识——C和C++的主要区别》

    C和C 43 43 的主要区别 设计思想上 xff1a C 43 43 是面向对象的语言 xff0c 而C是面向过程的结构化编程语言 语法上 xff1a C 43 43 具有封装 继承和多态三种特性 C 43 43 相比C xff0c 增加
  • 数据库原理及应用(十三)E-R图、关系模式

    数据库设计的过程 数据分析 gt 数据建模 gt 关系数据库模式 gt 关系数据库管理 用户需求 gt 概念模型 E R Model gt 逻辑模型 xff08 三层结构 xff09 现实世界 gt 信息世界 gt 机器世界 概念设计工具E
  • Ubuntu数据备份与恢复工具(一)

    在我们日常工作中 xff0c 个人文件 业务数据及应用信息的备份与恢复策略是一个重要的环节 意外删除 硬件故障 操作失误 网络攻击 xff0c 甚至是自然灾害 xff0c 都可以直接或间接导不可估价的数据损失 为了避免损失 xff0c 缩少
  • 百度移动端面试回忆

    百度一面 xff1a 1 自我介绍 2 悲观锁和乐观锁 乐观锁 xff1a 总是认为不会产生并发问题 xff0c 每次去取数据的时候总认为不会有其他线程对数据进行修改 xff0c 因此不会上锁 xff0c 但是在更新时会判断其他线程在这之前
  • Quagga编译安装

    Quagga源码编译安装 1 Quagga下载 1 官网下载quagga 1 2 4 tar gz并拖入虚拟机桌面 2 解压到 opt目录下 sudo tar zxvf Desktop quagga 1 2 4 tar gz C opt 2
  • VINS-FUSION 源码 双目 单线程 按执行顺序阅读

    VINS FUSION 源码 双目 单线程 按执行顺序阅读 Keywords xff1a VINS FUSION vins 源码解读 源码梳理 vins数据结构 vinsfusion vins双目 双目vins 双目vinsfusion 双
  • 【C语言】__attribute__使用

    一 介绍 GNU C 的一大特色就是 attribute 机制attribute 可以设置函数属性 xff08 Function Attribute xff09 变量属性 xff08 Variable Attribute xff09 和类型
  • Ubuntu20.04下CUDA、cuDNN的详细安装与配置过程(图文)

    Ubuntu20 04下CUDA cuDNN的详细安装与配置过程 xff0c 亲测试可用 xff08 图文 xff09 一 NVIDIA xff08 英伟达 xff09 显卡驱动安装1 1 关闭系统自带驱动nouveau2 2 NVIDIA
  • 使用动量(Momentum)的SGD、使用Nesterov动量的SGD

    使用动量 Momentum 的SGD 使用Nesterov动量的SGD 参考 xff1a 使用动量 Momentum 的SGD 使用Nesterov动量的SGD 一 使用动量 Momentum 的随机梯度下降 虽然随机梯度下降是非常受欢迎的
  • Data Uncertainty Learning in Face Recognition

    Data Uncertainty Learning in Face Recognition 建模数据的不确定性对含噪音图像非常重要 xff0c 但对于人脸识别的研究却很少 先驱者的工作 35 通过将每个人脸图像嵌入建模为高斯分布来考虑不确定
  • ENAS代码解读

    ENAS代码解读 参考代码 xff1a https github com TDeVries enas pytorch 数据集 xff1a cifar10 main函数 xff1a span class token keyword def s
  • PC-DARTS Partial Channel Connections for Memory-Efficient Differentiable Architecture Search

    PC DARTS Partial Channel Connections for Memory Efficient Differentiable Architecture Search Abstract 可微体系结构搜索 xff08 DAR
  • deepsort代码解析

    DeepSort代码解析 项目地址 xff1a deepsort span class token keyword if span name span class token operator 61 61 span span class t
  • CBAM

    CBAM 我们提出了卷积块注意力模块 xff08 CBAM xff09 xff0c 这是一个简单而有效的前馈卷积神经网络的注意力模块 给定一个中间特征图 xff0c 我们的模块沿着通道和空间两个独立的维度依次推导注意力图 xff0c 然后将
  • onos2.0编译安装(npm install和 build问题解决)

    onos编译安装 Ubuntu16 04 1 前置下载安装 1 1 前置包安装 sudo apt get install git sudo apt get install python Oracle JDK8 sudo apt get in
  • iDLG Improved Deep Leakage from Gradients

    iDLG Improved Deep Leakage from Gradients 人们普遍认为 xff0c 在分布式学习系统中 xff0c 如协作学习和联合学习等 xff0c 共享梯度不会泄露私人训练数据 最近 xff0c Zhu等人 1

随机推荐