前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python--装饰器详解

python--装饰器详解

原创
作者头像
languageX
发布2023-04-03 20:35:06
4910
发布2023-04-03 20:35:06
举报
文章被收录于专栏:计算机视觉CV计算机视觉CV

本文主要介绍python中的装饰器@的使用。

首先我们实现两个排序算法,如果想统计耗时比较,需要在每个算法中添加耗时统计逻辑。如果有10个排序算法,每个算法中都需要重复这种逻辑操作。

代码语言:javascript
复制
def bubble_sort(nums):
    """ 冒泡排序
    :param nums: 原始数组
    :return: 排序后数组
    """
    start = time.time()
    N = len(nums)
    if N <= 1:
        return  nums
    for i in range(0, N):
        for j in range(i, N):
            if nums[j] < nums[i]:
                nums[j], nums[i] = nums[i], nums[j]
    end = time.time()
    print("bubble sort cost", end - start)
    return nums
代码语言:javascript
复制
def quick_sort(nums):
    """ 快速排序
    :param nums: 原始数组
    :return: 排序后数组
    """
    def partition(arr, left, right):
        """ 分区操作,左边都是小于key索引的值,右边都是大于key索引的值
        :param arr: 待排序数组
        :param left: 数组最左索引
        :param right: 数组最右索引
        :return: 返回目前基准所在位置的索引
        """
        key = left
        while left < right:
            while left < right and arr[right] >= arr[key]:
                right -= 1
            while left < right and arr[left] <= arr[key]:
                left += 1
            arr[left], arr[right] = arr[right], arr[left]
        arr[left], arr[key] = arr[key], arr[left]
        return left

    def __sort(arr, left, right):
        if left >= right:
            return
        mid = partition(arr, left, right)
        # 递归调用sort
        __sort(arr, left, mid - 1)
        __sort(arr, mid + 1, right)

    start = time.time()
    N = len(nums)
    if N <= 1:
        return nums
    __sort(nums, 0, N - 1)
    end = time.time()
    print("quick sort cost", end - start)
    return nums

调用统计耗时:

代码语言:javascript
复制
nums = np.random.randint(10000,size=10000)
nums_copy = nums.copy()
quick_sort(nums)
bubble_sort(nums_copy)

最容易的优化方法,将测试耗时代码抽出来,函数作为参数执行:

代码语言:javascript
复制
def test_time(func, *args):
    start = time.time()
    out = func(*args)
    end = time.time()
    print("{} cost {}".format(func.__name__, end - start))
    return out
    
test_time(quick_sort, nums) 
test_time(bubble_sort, nums_copy)

缺点:不够简洁易读,在调用代码时,要在每个需要统计耗时的函数地方去显示调用这个封装的接口。

改善:进行封装,使用装饰器 @test_time_wrapper。这样调用quick_sort()接口时会执行test_time_wrapper()。我们只需要在函数上加一个装饰器,实现上面直接调用test_time(quick_sort, nums)的逻辑。

代码语言:javascript
复制
def test_time_wrapper(func):
    print("调用装饰函数", func.__name__)
    def wrapper(*args, **kwargs):
        """ docstring:调用装饰器函数 """
        start = time.time()
        out = func(*args, **kwargs)
        end = time.time()
        print("{} cost {}".format(func.__name__, end - start))
        return out
    return wrapper
    
@test_time_wrapper
def bubble_sort(nums):

@test_time_wrapper
def quick_sort(nums):

调用显示结果

代码语言:javascript
复制
quick_sort(nums)
bubble_sort(nums_copy)
print(quick_sort.__name__, quick_sort.__doc__)
print(bubble_sort.__name__, bubble_sort.__doc__)

输出:
调用装饰函数 bubble_sort
调用装饰函数 quick_sort
quick_sort cost 0.04823803901672363
bubble_sort cost 11.860395908355713
wrapper  docstring:调用装饰器函数 
wrapper  docstring:调用装饰器函数 

缺点:这里我们有发现问题,调用的函数名都变成了wrapper,我们真实调用的应该是quick_sort和bubble_sort。

改善:使用装饰器@functools.wraps,这里@wraps装饰器的作用就是为了保证被装饰器装饰后的函数还拥有原来的属性。

Python装饰器(decorator)在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变),为了不影响,Python的functools包中提供了一个叫wraps的decorator来消除这样的副作用。写一个decorator的时候,最好在实现之前加上functools的wrap,它能保留原有函数的名称和docstring。

改进代码如下,在上面基础上,我们加了第三行的 @wraps(func)

代码语言:javascript
复制
from functools import wraps
def test_time_wrapper(func):
    print("调用装饰函数", func.__name__)
    @wraps(func)
    def wrapper(*args, **kwargs):

再次执行,发现docstring变成了我们的目标函数。

代码语言:javascript
复制
quick_sort(nums)
bubble_sort(nums_copy)
print(quick_sort.__name__, quick_sort.__doc__)
print(bubble_sort.__name__, bubble_sort.__doc__)
输出:
调用装饰函数 bubble_sort
调用装饰函数 quick_sort
quick_sort cost 0.04761934280395508
bubble_sort cost 11.680961608886719
quick_sort  
     快速排序
    :param nums: 原始数组
    :return: 排序后数组
    
bubble_sort
     冒泡排序
    :param nums: 原始数组
    :return: 排序后数组

现在调用的函数名和docstring就是我们期望的真实的目标函数了。

test_wrapper_time函数接受的参数是我们想调用的fun,如果想对test_wrapper_time传入参数,可以再嵌套一层接口

代码语言:javascript
复制
def test_time_wrapper_arg(*args, **kwargs):
    def test_time_wrapper(func):
        print("test_time_wrapper func 装饰函数的参数", *args, **kwargs)
        @wraps(func)
        def wrapper(*args_wraps, **kwargs_wraps):
            """ 装饰器函数 """
            start = time.time()
            print("wrapper func 的参数", *args_wraps, **kwargs_wraps)
            out = func(*args_wraps, **kwargs_wraps)
            end = time.time()
            print("{} cost {}".format(func.__name__, end - start))
            return out
        return wrapper
    return test_time_wrapper  
      
@test_time_wrapper_arg("arg3", "arg4")
def bubble_sort(nums):

@test_time_wrapper_arg("arg1", "arg2")
def quick_sort(nums):

调用查看输出

代码语言:javascript
复制
quick_sort(nums)
bubble_sort(nums_copy)
输出
test_time_wrapper func 装饰函数的参数 arg3 arg4
test_time_wrapper func 装饰函数的参数 arg1 arg2
wrapper func 的参数 [3788 4670 7580 ... 6109 9852 2822]
quick_sort cost 0.057997703552246094
wrapper func 的参数 [3788 4670 7580 ... 6109 9852 2822]
bubble_sort cost 11.600555181503296

这里要注意test_time_wrapper func的参数和wrapper的参数的作用域。

调用顺序:test_time_wrapper_arg-->test_time_wrapper-->wraps-->func

quick_sorts的参数nums是传给目标函数本身的。

@test_time_wrapper_arg("arg3", "arg4")的参数是传入到test_time_wrapper_arg的。

参考:

https://zhuanlan.zhihu.com/p/45535784

https://zhuanlan.zhihu.com/p/78500405

https://huachao.blog.csdn.net/article/details/124422458?spm=1001.2014.3001.5502

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档