【18/07/25】对Python装饰器的研究

本文详细探讨了Python装饰器的设计原理及其应用场景,通过逐步实验解释了如何利用闭包及高阶函数实现装饰器,最终达到为已有函数添加新功能的目的。

聊这个之前先得弄明白2点:

装饰器是干嘛的?

为什么要这个东西?

装饰器的作用,是给一个已经写好的函数增加一些新的功能。在实现这个功能的同时,保证

  • 不能修改被装饰的函数的源代码
  • 不能修改被装饰的函数的调用方式。

我们来试试怎么才能实现这个需求。先定义一个函数,

def test():
    print('2018-07-25')

不妨假设我们的需求是在执行test函数体之前,打印出一行‘装饰一哈’。

定义一个函数deco试试,

def deco():
    print('装饰一哈')
    test()

这样装饰的功能就实现了,但是我们还需要满足其它的需求——不能修改被装饰函数的调用方式——也就是说,我们在完工之后,输入test(),就可以完成现在deco()的功能,

直接把deco赋值给test行不行?

def test():
    print('2018-07-25')

def deco():
    print('装饰一哈')
    test()

test = deco

test()

C:\Users\boTTle\PycharmProjects\Ironac\venv\Scripts\python.exe C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py
装饰一哈
装饰一哈
.
.
.
装饰一哈
装饰一哈
Traceback (most recent call last):
  File "C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py", line 10, in <module>
    test()
  File "C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py", line 6, in deco
    test()
  File "C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py", line 6, in deco
    test()
  File "C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py", line 6, in deco
    test()
  [Previous line repeated 992 more times]
  File "C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py", line 5, in deco
    print('装饰一哈')
RecursionError: maximum recursion depth exceeded while calling a Python object

Process finished with exit code 1

死循环了,test函数不停地调用自己,好蠢。

我们再试一种方法,把deco()的返回值定义为test函数体,并赋值给之前的变量名test,这样行不行?

def test():
    print('2018-07-25')

def deco():
    print('装饰一哈')
    return test

test = deco()

test()

C:\Users\boTTle\PycharmProjects\Ironac\venv\Scripts\python.exe C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py
装饰一哈
2018-07-25

Process finished with exit code 0

咦,好像可以哦,再多运行一遍test()试试,

def test():
    print('2018-07-25')

def deco():
    print('装饰一哈')
    return test

test = deco()

test()
test()

C:\Users\boTTle\PycharmProjects\Ironac\venv\Scripts\python.exe C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py
装饰一哈
2018-07-25
2018-07-25

Process finished with exit code 0

emmmm....不对劲啊,运行一遍test()只多输出了一个‘2018-07-25’,也就是说——‘装饰一哈’是test = deco()语句执行时输出的——搞了半天test还是原来的test....

在IDLE环境里能看得更直观,

>>> def test():
    print('2018-07-25')

    
>>> def deco():
    print('装饰一哈')
    return test

>>> test = deco()
装饰一哈
>>> test
<function test at 0x03130DB0>
>>> test()
2018-07-25
>>> deco()
装饰一哈
<function test at 0x03130DB0>
>>> 

这里就有点奇怪了,既然我执行了语句test = deco(), 那为什么输入test 跟输入deco() 的输出物完全不一样呢?

验一下身份证,

>>> test is deco()
装饰一哈
True
>>> id(test)
51580336
>>> id(deco())
装饰一哈
51580336
>>> 

还是一样!

但是我们发现了一件事情,只要语句里面有deco(),输出结果里就一定会多一个‘装饰一哈’....

陷入深深地思考之后,我发现了,原来系统判定test和deco()一样,是判定deco()的返回值和test一样,并且之前执行test = deco()的时候,也仅仅只是将deco()的返回值赋给了test,而deco函数体在从被调用到给出返回值之间的这个计算过程,是不会赋给test的。

这个计算过程是赋不了值的,举个更清晰的例子

>>> 8 is 3 + 5
True

8 虽然is 3 + 5,但是3 + 5 是执行过计算的,8就是8。

完成装饰器之后,我们需要每调用一次test(),‘装饰一哈’就要被输出一次,这时我们就想,能不能把刚才的deco闭个包再看怎么赋值给test?

def test():
    print('2018-07-25')

def cover():
    def deco():
        print('装饰一哈')
        test()
    return deco

test = cover()
test()

C:\Users\boTTle\PycharmProjects\Ironac\venv\Scripts\python.exe C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py
装饰一哈
装饰一哈
.
.
.
装饰一哈
装饰一哈
Traceback (most recent call last):
  File "C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py", line 11, in <module>
    test()
  File "C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py", line 7, in deco
    test()
  File "C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py", line 7, in deco
    test()
  File "C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py", line 7, in deco
    test()
  [Previous line repeated 992 more times]
  File "C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py", line 6, in deco
    print('装饰一哈')
RecursionError: maximum recursion depth exceeded while calling a Python object

Process finished with exit code 1

又死循环了....好蠢.....

要想个办法不让test调用自己....同时我们想到,这个装饰器不应该是单单为test这个函数设计的,应该所有的函数体都能适用——那么思路就来了,需要在装饰器上增加一个形参,并将函数体内的test改成对应的形参名——再试试,

def test():
    print('2018-07-25')

def cover(func):
    def deco():
        print('装饰一哈')
        func()
    return deco

test = cover()
test()

C:\Users\boTTle\PycharmProjects\Ironac\venv\Scripts\python.exe C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py
Traceback (most recent call last):
  File "C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py", line 10, in <module>
    test = cover()
TypeError: cover() missing 1 required positional argument: 'func'

Process finished with exit code 1

test = cover()这句话里面少了一个位置参数,OK,改一下,test = cover(test)

def test():
    print('2018-07-25')

def cover(func):
    def deco():
        print('装饰一哈')
        func()
    return deco

test = cover(test)

test()

C:\Users\boTTle\PycharmProjects\Ironac\venv\Scripts\python.exe C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py
装饰一哈
2018-07-25

Process finished with exit code 0

nice!!

多运行几次test()看看,

def test():
    print('2018-07-25')

def cover(func):
    def deco():
        print('装饰一哈')
        func()
    return deco

test = cover(test)

test()
test()
test()

C:\Users\boTTle\PycharmProjects\Ironac\venv\Scripts\python.exe C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py
装饰一哈
2018-07-25
装饰一哈
2018-07-25
装饰一哈
2018-07-25

Process finished with exit code 0

破费。

按照这个思路,我们再试试不对deco()进行闭包操作行不行?

def test():
    print('2018-07-25')

def deco(func):
    print('装饰一哈')
    return func

test = deco(test)

test()
test()

C:\Users\boTTle\PycharmProjects\Ironac\venv\Scripts\python.exe C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py
装饰一哈
2018-07-25
2018-07-25

Process finished with exit code 0

不行,不闭包只增加一个形参,跟之前没区别。

那么装饰器的形态大概就出来了,最后在deco()函数体内增加*args和**kw参数,增加泛用性。
 

def test():
    print('2018-07-25')

def cover(func):
    def deco(*args, **kw):
        print('装饰一哈')
        func(*args, **kw)
    return deco


test = cover(test)

test()

C:\Users\boTTle\PycharmProjects\Ironac\venv\Scripts\python.exe C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py
装饰一哈
2018-07-25

Process finished with exit code 0

 

最最后,再把test = cover(test)用一个现成的语法糖@cover代替,并把test函数体放在@cover下面。

为什么会有这个语法糖?

为什么要放在下面?

我暂时不想研究了。

def cover(func):
    def deco(*args, **kw):
        print('装饰一哈')
        func(*args, **kw)
    return deco

@cover
def test():
    print('2018-07-25')

test()

C:\Users\boTTle\PycharmProjects\Ironac\venv\Scripts\python.exe C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py
装饰一哈
2018-07-25

Process finished with exit code 0

大功告成。

最最最后,有个小疑问,

deco函数体内的func(*args, **kw)也可以写成return func(*args, **kw)

def cover(func):
    def deco(*args, **kw):
        print('装饰一哈')
        return func(*args, **kw)
    return deco

@cover
def test():
    print('2018-07-25')

test()

C:\Users\boTTle\PycharmProjects\Ironac\venv\Scripts\python.exe C:/Users/boTTle/PycharmProjects/Ironac/Another_test.py
装饰一哈
2018-07-25

Process finished with exit code 0

不知道这两种在实际运用时有撒区别.....但这篇就先这样吧....后面有问题再说886


——只要原test函数要求带返回值,那么就deco函数体内就必须写成return func(*args, **kw)。

——18/9/27:

装饰器的作用为对现有程序添加新功能,而新功能的执行必须是一段过程代码,原功能则可通过函数体中调用(过程代码)的方式,或者返回值来实现。

而要实现每次调用时新功能都被执行,则这段代码必须要被闭包,并在外层增加一层调用,如此才能实现。因此装饰器必须用高阶函数实现。

 

【激光质量检测】利用丝杆与步进电机的组合装置带动光源的移动,完成对光源使用切片法测量其光束质量的目的研究(Matlab代码实现)内容概要:本文研究了利用丝杆与步进电机的组合装置带动光源移动,结合切片法实现对激光光源光束质量的精确测量方法,并提供了基于Matlab的代码实现方案。该系统通过机械装置精确控制光源位置,采集不同截面的光强分布数据,进而分析光束的聚焦特性、发散角、光斑尺寸等关键质量参数,适用于高精度光学检测场景。研究重点在于硬件控制与图像处理算法的协同设计,实现了自动化、高重复性的光束质量评估流程。; 适合人群:具备一定光学基础知识和Matlab编程能力的科研人员或工程技术人员,尤其适合从事激光应用、光电检测、精密仪器开发等相关领域的研究生及研发工程师。; 使用场景及目标:①实现对连续或脉冲激光器输出光束的质量评估;②为激光加工、医疗激光、通信激光等应用场景提供可靠的光束分析手段;③通过Matlab仿真与实际控制对接,验证切片法测量方案的有效性与精度。; 阅读建议:建议读者结合机械控制原理与光学测量理论同步理解文档内容,重点关注步进电机控制逻辑与切片数据处理算法的衔接部分,实际应用时需校准装置并优化采样间距以提高测量精度。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值