python 装饰器

目录

    • 一、标准的装饰器函数怎么写
    • 二、打印被装饰函数的元数据(即函数属性)
    • 三、带参数的装饰器

装饰器在python中非常重要,面试中也是必考的问题,必须要搞懂。这篇博客的很多内容来自《Python 进阶编程:编写更高效、优雅的Python代码》一书。

在程序中存在高度重复的代码时,我们就需要考虑使用元编程了,主要技术有装饰器、类装饰器和元类。

一个装饰器就是一个函数,它将一个函数作为参数并返回一个新的函数。
        

一、标准的装饰器函数怎么写

下面的函数中,装饰器的目的是计算任意一个函数的运行时间。这是一个很简单的装饰器,但是其中有很多细节值得注意。

wrap的意思是包装、包裹,@wrap是python的一个装饰器工具。

import time
from functools import wraps

def time_use(func):    # 接收函数 func()作为参数
    @wraps(func)       # @wraps的作用是使装饰器保留原始函数的元数据,不改变其结构和属性,如 name、doc等
    def wrapper(*args, **kwargs):    # *args, **kwargs不能少,确保 wrapper()可以接收任意参数的函数
        start = time.time()
        result = func(*args, **kwargs)    # 为了保证 wrapper()代码不变,这里的 func()也需要保证接收任意参数
        end = time.time()
        print(f"function name: {func.__name__}(),  time use: {end - start:0.5f}s")
        return result
    return wrapper    # 装饰器返回的是一个函数

@time_use     # @装饰符的作用是将 count()作为 time_use()的参数
def count(n):
    while n > 0:
        n -= 1

if __name__ == '__main__':
    count(100000)     # 注意这里执行的是原始函数 count(),而不是装饰器 @time_use(),但实现的是装饰器的功能。
    count(10000000)


# output:
function name: count(),  time use: 0.00997s
function name: count(),  time use: 0.77194s

其中*args和**kwargs是可变长参数,*args用于让函数接收任意数量的位置参数,这些位置参数构成一个tuple;**kwargs用于让函数接收任意数量的关键字参数,这些关键字参数构成一个dict。

可变长参数适用于为函数添加装饰器、参数数目不确定、实现函数多态、继承情况下子类需要调用父类的某些方法等情形。

关于可变长参数的更多介绍可见《Python 进阶编程》7.2节。
        

二、打印被装饰函数的元数据(即函数属性)

func. __ name __ 的作用是返回函数名。

func. __ doc __ 的作用是返回文件开头注释的内容,即文档字符串DocString。

func. __ annotations __ 的作用是返回一个key为函数形参、value为形参的数据类型注解的字典。

如下面的例子中,def count(m, n: float) -> None 函数中,参数 n和返回值有数据类型注解,所以 __ annotations __ 只返回参数 n和返回值的dict,并不返回参数m 的dict。因为m没有value值,无法组成字典。

这些都是在是在使用了 @wrap的情况下实现的,如果没有用 @wrap,原始函数会丢失所有有用的元数据信息。

代码示例:

import time
from functools import wraps

def time_use(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"function name: {func.__name__},  time use: {end - start:0.5f}s")
        return result
    return wrapper

@time_use
def count(m, n: float) -> None:
    """
    :param m: any data type
    :param n: float, 注意 pyhton中没有 double类型
    :return: None
    """
    while m + n > 0:
        n -= 1


if __name__ == '__main__':
    count(10, 100000)
    print(f'function name: {count.__name__}')
    print(f'function doc: {count.__doc__}')
    print(f'function annotations: {count.__annotations__}')


# output:
function name: count,  time use: 0.01896s
function name: count
function doc:
    : param m : any data type
    : param n: float,注意pyhton中没有double类型
    : return: None

function annotations: {'n': <class 'float'>, 'return': None}

关于python函数的更多属性可见博客:详解Python中函数和模块的特殊属性

        

三、带参数的装饰器

比如下面的例子,想要在 time_use() 中增加一个线程休眠的参数,就需要在 time_use() 中实现一个内部装饰器,整体变成三个函数三个 return的装饰器。但是其基本思想很简单:最外层的函数接收参数并将参数作用在内部的装饰器函数中。

这时就不能写成 @time_use了,参数也要带上,写成 @time_use(3)

import time
from functools import wraps

def time_use(sleep_time):   # 带参数的装饰器
    def decotrate(func):    # 增加一层函数,写成内部装饰器
        @wraps(func)
        def wrapper(*args, **kwargs):
            start = time.time()
            result = func(*args, **kwargs)
            time.sleep(sleep_time)    # 线程休眠
            end = time.time()
            print(f"function name: {func.__name__},  time use: {end - start:0.5f}s")
            return result
        return wrapper
    return decotrate

@time_use(3)     # 装饰器也要带参数,等价于 time_use(3)(count)
def count(m, n):
    while m + n > 0:
        n -= 1


if __name__ == '__main__':
    count(10, 100000)


# output:
function name: count,  time use: 3.02121s

你可能感兴趣的:(python,python,开发语言)