分享

python - 常用的装饰器 decorator 有哪些?

 网摘文苑 2023-03-21 发表于新疆

python编程中使用装饰器(decorator)工具,可以使代码更简洁清晰,提高代码的重用性,还可以为代码维护提供方便。对于python初学者来说,根据装饰器 (decorator )的字面意思并不好理解这玩意到底是个什么东西,在编写代码时运用起来也有些困难。其实,可以简单地把它理解为调用另一个函数的函数,或者可以这样理解:装饰器就像一个人跑步时随身携带的计时器、计数器或计速器。本文以简短的代码段为例,介绍几种常用的装饰器 decorator,希望能对这一工具的概念理解和应用起到抛砖引玉的作用。

@timeit ,计时器

用于度量执行某个函数或功能程序包所耗费的时间,在调试或监测程序时经常会用到。

示例代码:

import timefrom functools import wrapsdef timeit(func): @wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() result = func(*args, **kwargs) end = time.perf_counter() print(f'执行 {func.__name__} 函数共耗时 {end - start:.6f} 秒。') return result return wrapper@timeitdef process_data(): time.sleep(1)process_data()

输出结果:

执行 process_data 函数共耗时 1.005707 秒。

@countcall,计数器

用于记录某个函数被调用的次数。

示例代码如下:

from functools import wrapsdef countcall(func): @wraps(func) def wrapper(*args, **kwargs): wrapper.count += 1 result = func(*args, **kwargs) print(f' 函数 {func.__name__} 已被调用 {wrapper.count} 次!') return result wrapper.count = 0 return wrapper@countcalldef process_data(): passprocess_data()process_data()process_data()

输出结果:

函数 process_data 已被调用 1 次!函数 process_data 已被调用 2 次!函数 process_data 已被调用 3 次!

@logger,日志记录

用于记录程序或函数的动作等信息。

示例代码:

def logger(function): def wrapper(*args, **kwargs): print(f'----- {function.__name__}: 开始执行 -----') output = function(*args, **kwargs) print(f'----- {function.__name__}: 结束 -----') return output return wrapper
@loggerdef test_function(text):    print(text)test_function('第一次测试')

输出结果:

----- test_function: 开始执行 -----第一次测试----- test_function: 结束 -----

再执行一次:

test_function('第二次测试')

输出结果:

----- test_function: 开始执行 -----第二次测试----- test_function: 结束 -----

@wraps,打包器

该装饰器用于更新打包函数,使包内函数保持初始状态并继承该函数的名称和属性。

示例代码(其中用到前面logger装饰器):

def logger(function):    def wrapper(*args, **kwargs):          ''' 打包器 wrapper 说明 '''        print(f'-----函数 {function.__name__}: 开始打包执行 -----')        output = function(*args, **kwargs)        print(f'-----函数 {function.__name__}: 结束 -----')        return output    return wrapper@loggerdef add_two_numbers(a, b):     ''' 本函数功能:求两个数之和 '''    return a + badd_two_numbers(1,2)

输出结果:

-----函数 add_two_numbers: 开始打包执行 ----------函数 add_two_numbers: 结束 -----

可以使用语句'add_two_numbers.__name__' 查看对该函数进行打包的打包器的名称,执行后的输出结果:wrapper;也可以使用语句'add_two_numbers.__doc__’ 查看对该函数进行打包的打包器的说明,执行后的输出结果:' 打包器 wrapper 说明 '

@repeat,函数重复执行

指定某个函数重复执行的次数,可以用于代码调试、压力测试或任务自动重复执行。

示例代码:

def repeat(number_of_times):    def decorate(func):        @wraps(func)        def wrapper(*args, **kwargs):            for _ in range(number_of_times):                func(*args, **kwargs)        return wrapper    return decorate@repeat(3)def dummy():    print('我是repeat装饰器!')dummy()    

输出结果:

我是repeat装饰器!我是repeat装饰器!我是repeat装饰器!

@lru_cache,缓存器

这是一个python的内置装饰器,可以从funtools 中导入。该装饰器用于监测运行时间较长的任务的返回值,如查询数据库、对远程网页发出请求或一些数据处理量大的任务,所使用的算法是LRU(least-recently-used),一旦缓存占满,则丢弃哪些未被使用或很少使用的返回值。

示例代码:

import randomimport timefrom functools import lru_cache@lru_cache(maxsize=None)def heavy_processing(n):    sleep_time = n + random.random()    time.sleep(sleep_time)

第一次运行 heavy_processing() 函数:

 %%timeheavy_processing(0)

输出结果:

CPU times: total: 0 nsWall time: 816 ms

接下来,再运行 heavy_processing() 函数两次,执行代码和第一次相同,输出结果分别为:

CPU times: total: 0 nsWall time: 0 ns
CPU times: total: 0 nsWall time: 0 ns

从三次运行结果看,除第一次执行耗时较长之外,后续执行相同的代码,耗时都大大少于第一次运行的耗时。

@retry,强制执行器

某个函数执行发生异常时,强制执行多次。该装饰器有点类似循环,有三个参数:强制执行的指定次数,捕捉异常,两次强制执行之间的时间间隔。每次迭代执行,都调用 try 或 except 代码块中的代码,调用成功则中断循环,否则就间歇指定的时长,然后再执行。如果完成了循环的最后一次迭代,代码块调用都不成功,那么其中的 打包器(@wrapper)就给出异常信息。

示例代码:

import randomimport timefrom functools import wrapsdef retry(num_retries, exception_to_check, sleep_time=0): def decorate(func): @wraps(func) def wrapper(*args, **kwargs): for i in range(1, num_retries+1): try: return func(*args, **kwargs) except exception_to_check as e: print(f'{func.__name__} 函数发生 {e.__class__.__name__} 错误,再试...') if i < num_retries: time.sleep(sleep_time) raise e return wrapper return decorate@retry(num_retries=3, exception_to_check=ValueError, sleep_time=1)def random_value(): value = random.randint(1, 5) if value == 3: raise ValueError('数值不能等于3') return value

把 random_value() 函数执行两次,因函数输出随机值,将会输出类似下面的结果:

random_value 函数发生 ValueError 错误,再试...4
5

@rate_limited,限速器

该装饰器用于设定调用某个函数的频率,防止函数被频繁地调用。

示例代码:

import timefrom functools import wrapsdef rate_limited(max_per_second):    min_interval = 1.0 / float(max_per_second)    def decorate(func):        last_time_called = 0.0        @wraps(func)        def rate_limited_function(*args, **kargs):            elapsed = time.perf_counter() - last_time_called            left_to_wait = min_interval - elapsed            if left_to_wait > 0:                time.sleep(left_to_wait)            ret = func(*args, **kargs)            last_time_called = time.perf_counter()            return ret        return rate_limited_function    return decorate

@dataclass,类装饰器

自python 3.7 引入标准款,用于装饰对象的类型,会自动生成一些方法,如__init__, __repr__, __eq__, __lt__, 和 __str__ ,这些方法都是针对存储数据,以便提高数据的可读性并为代码维护提供便利。

示例代码:

from dataclasses import dataclass@dataclassclass Person: first_name: str last_name: str age: int job: str def __eq__(self, other): if isinstance(other, Person): return self.age == other.age return NotImplemented def __lt__(self, other): if isinstance(other, Person): return self.age < other.age return NotImplementedjohn = Person(first_name='张', last_name='兵', age=30, job='医生',)anne = Person(first_name='晓晓', last_name='刘', age=40, job='工程师',)

输出结果:

FalseTruePerson(first_name='晓晓', last_name='刘', age=40, job='工程师')

@register,注册器

在执行python代码时,如果执行过程出现悬挂,需要保存任务信息或打印提示信息,就可以用@register。

示例代码:

from atexit import register@registerdef terminate(): perform_cleanup_task() print('再见!')while True: print('你好!')

以上代码中,perform_cleanup_task() 函数没有定义,执行这段代码会一直输出“你好”这个信息,执行就出现悬挂状态,按ctrl+C 中断该循环,将会输出“再见!”这条信息。

(本文完)

文章图片1

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    热点新闻
    类似文章 更多