Python修饰器和修饰函数

一、修饰器的起缘

编写测试某个函数时,往往需要给这个函数添加某些额外的功能。比方说下面这个sort_test函数:

import random

def sort_test():
    orin_list=[random.randrange(0,100) for i in range(0,20)]
    sorted_list=sorted(orin_list)
    print(f"排序前:{orin_list}")
    print(f"排序后:{sorted_list}")

sort_test()

如果我想测试这个sort_test函数运行时长,你可以在这个函数中添加测试语句,如下:

import random
from time import time

def sort_test():
    t1=time()
    orin_list=[random.randrange(0,100) for i in range(0,20)]
    sorted_list=sorted(orin_list)
    print(f"排序前:{orin_list}")
    print(f"排序后:{sorted_list}")
    t2=time()
    print(f"耗时:{t2-t1}")

sort_test()

排序前:[14, 77, 34, 11, 20, 37, 97, 84, 92, 48, 63, 52, 68, 80, 69, 53, 93, 46, 98, 48]
排序后:[11, 14, 20, 34, 37, 46, 48, 48, 52, 53, 63, 68, 69, 77, 80, 84, 92, 93, 97, 98]
耗时:0.0

这样操作的确能够获知函数的运行时长,达到我们的目的。但这个操作无疑破坏了被测试的函数。这显然不符合面向对象编程的习惯。

所以,修饰器和修饰函数应运而生。

我们可以如下编写代码操作:

import random
from time import time

def runtime_of_sorttest(func):
    def warp():
        t1=time()
        func()
        t2=time()
        print(t2-t1)
    return warp

@runtime_of_sorttest
def sort_test():
    orin_list=[random.randrange(0,100) for i in range(0,20)]
    sorted_list=sorted(orin_list)
    print(f"排序前:{orin_list}")
    print(f"排序后:{sorted_list}")

sort_test()

排序前:[52, 54, 36, 27, 63, 37, 24, 15, 80, 74, 42, 33, 65, 30, 33, 75, 91, 49, 23, 59]
排序后:[15, 23, 24, 27, 30, 33, 33, 36, 37, 42, 49, 52, 54, 59, 63, 65, 74, 75, 80, 91]
0.0

 此般操作,相当于不直接执行sort_test函数,而是把sort_test函数作为一个参数传入修饰函数runtime_of_sorttest,然后由修饰函数及其嵌套函数决定其是否执行,何时执行。

具体的操作方法是,在sort_test函数定义前加上@runtime_of_sorttest(也就是@修饰函数名称)

二、修饰函数的规范

下面来介绍这个修饰函数的规范和具体的使用方法。

def name_of_decorate(func):
    def warp():    
        statement
        func()
        statement
    return warp

修饰函数至少包含一层嵌套,对于上面这个最简单的情况,修饰函数有一层嵌套。第一层函数即为修饰函数;第二层函数用于代替原函数执行,习惯上命名为warp。

最后,修饰函数需要返回warp,也就是第二层函数

三、更为复杂的修饰函数

显而易见,上面这个修饰函数仅仅适用于被修饰函数没有参数,且没有返回值的情况。那么被修饰函数有返回值,或被修饰函数有参数,抑或是被修饰函数既有返回值,又有参数的情况,该如何处理呢?下面对多种情况进行介绍:


1、无返回值、无参数

也即我们上面已经讨论的情况,规范如下:

def name_of_decorate(func):
    def warp():    
        statement
        func()
        statement
    return warp
2、无返回值、有参数

修饰函数warp函数这一层的参数列表会接收原函数的参数。规范如下:

​def name_of_decorate(func):
    def warp(param1,param2):    
        statement
        func(param1,param2)
        statement
    return warp

在warp处接收参数,也需要传入func()中执行!

具体例子如下,比方说,我想控制sort_test函数排序的列表长度,我可以以参数的形式传入。

import random
from time import time

def runtime_of_sorttest(func):
    def warp(param_len):
        t1=time()
        func(param_len)
        t2=time()
        print(t2-t1)
    return warp

@runtime_of_sorttest
def sort_test(length:int):
    orin_list=[random.randrange(0,100) for i in range(0,length)]
    sorted_list=sorted(orin_list)
    print(f"排序前:{orin_list}")
    print(f"排序后:{sorted_list}")

sort_test(5)

排序前:[24, 10, 11, 41, 44]
排序后:[10, 11, 24, 41, 44]
0.0

从结果来看,我们的目的达成了!

3、有返回值、无参数

warp函数的返回值会代替被修饰函数的返回值。

def name_of_decorate(func):
    def warp():    
        statement
        r=func()
        statement
        return r
    return warp

具体的,对于sort_test函数,如果它有返回值——列表长度。那么修饰函数应该如下设计:

import random
from time import time

def runtime_of_sorttest(func):
    def warp():
        t1=time()
        r=func()
        t2=time()
        print(t2-t1)
        return r
    return warp

@runtime_of_sorttest
def sort_test():
    orin_list=[random.randrange(0,100) for i in range(0,20)]
    sorted_list=sorted(orin_list)
    print(f"排序前:{orin_list}")
    print(f"排序后:{sorted_list}")
    return len(sorted_list)

print(sort_test())

排序前:[94, 74, 54, 3, 68, 46, 25, 44, 90, 30, 48, 43, 45, 1, 59, 80, 39, 59, 12, 46]
排序后:[1, 3, 12, 25, 30, 39, 43, 44, 45, 46, 46, 48, 54, 59, 59, 68, 74, 80, 90, 94]
0.0
20

4、有返回值、有参数

把2、3两种情况组合一下就行了:

def name_of_decorate(func):
    def warp(param1):    
        statement
        r=func(param1)
        statement
        return r
    return warp

具体样例如下:

import random
from time import time

def runtime_of_sorttest(func):
    def warp(param_len):
        t1=time()
        r=func(param_len)
        t2=time()
        print(t2-t1)
        return r
    return warp

@runtime_of_sorttest
def sort_test(length:int):
    orin_list=[random.randrange(0,100) for i in range(0,length)]
    sorted_list=sorted(orin_list)
    print(f"排序前:{orin_list}")
    print(f"排序后:{sorted_list}")
    return len(sorted_list)

print(sort_test(10))

有趣的是,wrap函数参数列表非常智能,你可以写成*args和**kwargs的形式:

import random
from time import time

def runtime_of_sorttest(func):
    def warp(*args,**kwargs):
        t1=time()
        print(f"param:{args} and {kwargs}")
        r=func(*args,**kwargs)
        t2=time()
        print(t2-t1)
        return r
    return warp

@runtime_of_sorttest
def sort_test(length:int):
    orin_list=[random.randrange(0,100) for i in range(0,length)]
    sorted_list=sorted(orin_list)
    print(f"排序前:{orin_list}")
    print(f"排序后:{sorted_list}")
    return len(sorted_list)

print(sort_test(10))

param:(10,) and {}
排序前:[18, 33, 63, 36, 8, 84, 51, 55, 42, 18]
排序后:[8, 18, 18, 33, 36, 42, 51, 55, 63, 84]
0.0
10

你可以通过*args,**kwargs传参,也可以把对应的元组和字典打印出来!

5、修饰函数本身有参数

对于这个情况,就要使用两层嵌套函数了,也就是说,一共有三层函数!

规范如下:

def name_of_decorate(param):
    def middle(func):
        def warp(*args,**kwargs):
            statement
            r=func(param)
            statement
            return r
        return warp
    return middle

在这种情况下,warp函数变为了第三层函数,作用还是接收参数;而第二层函数接替修饰函数起到传递函数的作用,而修饰函数本身传递其自己的参数。

下面,我想用修饰函数来控制sort_test函数的排序列表长度,我可以这么做:

import random
from time import time

def runtime_of_sorttest(length_d:int):
    def middle(func):
        def wrap(*argc,**kwargs):
            t1=time()
            r=func(length_d)
            t2=time()
            print("execute time:%f"%(t2-t1))
            print(f"original parameters: {argc} {kwargs}")
            return r
        return wrap
    return  middle

@runtime_of_sorttest(10)
def sort_test(length:int):
    orin_list=[random.randrange(0,100) for i in range(0,length)]
    sorted_list=sorted(orin_list)
    print(f"排序前:{orin_list}")
    print(f"排序后:{sorted_list}")
    return len(sorted_list)

sort_test(20)

排序前:[43, 99, 50, 40, 97, 42, 34, 70, 25, 76]
排序后:[25, 34, 40, 42, 43, 50, 70, 76, 97, 99]
execute time:0.000000
original parameters: (20,) {}

这很有趣,因为我在sort_test()处传递的参数是20,列表长度应该是20;但由于修饰参数传参为10,且func()恰恰用了这个参数,所以排序的列表长度变成了10!

事实上,原始的数据20也是被接收了,结果第四行我已经打印了参数列表的元组,说明原始参数也被接收了,只是我们没有使用它!

四、多层嵌套的修饰函数

在编程时,还会使用多次嵌套的修饰函数。但我们把它当作一个洋葱,由外至内地剥开,再由内至外地合上即可。

这么说肯定难以理解,看下面的具体例子:

def fun_d1(func):
    def warp():
        print("fun_d1 start")
        func()
        print("fun_d1 stop")
    return warp


def fun_d2(func):
    def warp():
        print("fun_d2 start")
        func()
        print("fun_d2 stop")
    return warp

@fun_d1
@fun_d2
def fun():
    print("fun execute")

fun()

fun_d1 start
fun_d2 start
fun execute
fun_d2 stop
fun_d1 stop

可以这样理解,@fun_d1修饰的是@fun_d2和fun函数组成的整体,先运行fun_d1中的wrap,直至发现func(),然后开始运行@fun_d2修饰函数。所以结果的最外层是fun_d1 start和fun_d1 stop。而里面的结果,是下面这个整体产生的:

@fun_d2
def fun():
    print("fun execute")

也就是说,fun_d1中的func参数,代表是@fun_d2和fun函数组成的整体,而fun_d2中的fun参数,代表的是fun啊哈纳树本身!

五、一种似是而非的理解

def fun_d(func):
    def warp():
        print("fun_d execute")
        func()
    return warp

@fun_d
def fun():
    print("fun execute")

fun()

有人把修饰函数理解为这样:

def fun_d(func):
    def warp():
        print("fun_d execute")
        func()
    return warp

def fun():
    print("fun execute")

fun_d(fun())

也就是说修饰函数等价于这个嵌套:

fun_d(fun())

但事实上,你可以这么类比,但不能这样等价:
如果你尝试运行上述两端代码,得出的结果是不同的!!!

fun_d execute
fun execute

 fun execute

而且,按照这样理解,应该先执行fun,再执行fun_d。但事实并非如此。如上,先打印了fun_d,再打印了fun。

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值