模块介绍

adorner 是一个现代轻量级的 Python 装饰器辅助模块。

目前该模块仅实现了 4 个类,对应着 4 个功能:制造装饰器执行计时函数缓存捕获重试

仓库地址:https://github.com/gupingan/adorner

安装

该模块可在上方仓库中的 Releases 页面下载 tar.gz 文件后离线安装,也可以通过包管理工具进行下载安装:

pip install adorner

也可以尝试下方这个命令:

pip install adorner -i http://mirrors.cloud.tencent.com/pypi/simple/

或者更换为: https://mirrors.cloud.tencent.com/pypi/simple/


Decorator

Decorator 类用于标记装饰器函数,使装饰器的构造更简单。它允许你定义一个装饰器并将其应用到函数上,简化了装饰器的创建和使用过程。

源码注释

class Decorator(object):
def __init__(self, decorator=None):
self.decorator = decorator or (lambda s: s.execute()) # 是传入的装饰器函数,如果没有传入,则默认使用一个简单的 lambda 函数,该函数调用 self.execute()。
self.function = None # 用于存储被装饰的函数。
# args 和 .kwargs 分别用于存储传递给被装饰函数的位置参数和关键字参数。
self.args = tuple()
self.kwargs = dict() def __call__(self, function):
"""
这里是关键部分
__call__ 可使得类的实例可以像函数一样被调用。
接收一个函数 function 作为参数,并返回一个 wrapper 函数。
wrapper 函数内部将被装饰的函数及其参数存储在 self.function、self.args 和 self.kwargs 中,然后调用 self.decorator(self)。
functools.wraps(function) 用于保持被装饰函数的元数据(如函数名和文档字符串)
"""
@functools.wraps(function)
def wrapper(*args, **kwargs):
self.function = function
self.args = args
self.kwargs = kwargs
return self.decorator(self) return wrapper def __repr__(self):
"""
返回对象的字符串表示形式,便于调试和查看对象信息。根据是否有被装饰的函数来返回不同的字符串。
"""
decorator_name = self.decorator_name.lstrip('<').rstrip('>')
if self.function:
return f'<{self.__class__.__name__}: {decorator_name} To {self.function.__name__}>'
return f'<{self.__class__.__name__}: {decorator_name}>' def execute(self, *args, **kwargs):
"""
用于执行被装饰的函数。会使用传入的参数(如果有)或存储的参数来调用 _execute_sync 方法,该方法应该是为了以后适配更复杂的异步装饰器所提前编写好的
"""
final_args = args if args else self.args
final_kwargs = kwargs if kwargs else self.kwargs
return self._execute_sync(final_args, final_kwargs) def _execute_sync(self, args, kwargs):
"""
同步地执行被装饰的函数,并返回其结果
"""
return self.function(*args, **kwargs) @property
def function_name(self):
"""返回被装饰函数的名称"""
if self.function:
return self.function.__name__
return '<None>' @property
def function_doc(self):
"""返回被装饰函数的文档字符串"""
if self.function:
return self.function.__doc__ or ''
return '' @property
def decorator_name(self):
"""返回装饰器的名称"""
if self.decorator:
return self.decorator.__name__
return '<None>' @property
def decorator_doc(self):
"""返回装饰器的文档字符串"""
if self.decorator:
return self.decorator.__doc__ or ''
return ''

示例用法

import time
from adorner import Decorator @Decorator
def exception_decorator(self: Decorator):
"""
捕获异常日志的装饰器
:param self: 装饰器 Decorator 实例
:return: 被修饰函数的执行结果
"""
print(self.function_doc) # 打印被装饰函数的文档
print(self.decorator_doc) # 打印装饰器的文档
print(self.function_name) # 打印被装饰函数的名称
print(self.decorator_name) # 打印装饰器的名称
print(self.args) # 打印被装饰函数的传入的位置参数 (默认形参值不包含)
print(self.kwargs) # 打印被装饰函数的传入的关键字参数 (默认形参值不包含)
try:
result = self.execute() # 打印 1
# 执行被装饰函数,不传入任何参数时,表示使用默认的参数 self.args、self.kwargs
# 可覆盖传入参数
self.execute(value=2) # 打印 2
self.execute(3) # 打印3 并抛出异常
return result
except Exception as e:
print(f"捕获异常: {e}")
raise @exception_decorator
def risky_function(value=1):
print(value)
if value == 3:
raise ValueError("出错了") try:
risky_function()
except ValueError:
pass # 捕获异常: 出错了

上述示例执行后,终端应该会输出:



    捕获异常日志的装饰器
:param self: 装饰器 Decorator 实例
:return: 被修饰函数的执行结果 risky_function
exception_decorator
()
{}
1
2
3
捕获异常: 出错了

Timer

Timer 类是 Decorator 类的一个子类,用于测量被装饰函数的执行时间。它继承了 Decorator 类的所有功能,并在执行函数时记录开始和结束的时间,以计算函数的执行时长,该类属于 Decorator 类的扩展使用。

源码注释

class Timer(Decorator):
def __init__(self, decorator=None):
super().__init__(decorator) # 调用父类 Decorator 的构造函数,初始化装饰器函数。
self.time = 0 # 用于存储被装饰函数的执行时间。 def execute(self, *args, **kwargs):
"""
执行被装饰的函数,并记录其执行时间。
使用 time.perf_counter() 记录开始和结束的时间,计算函数执行时长,并存储在 self.time 中。
"""
_start = time.perf_counter() # 记录开始时间。
result = super().execute(*args, **kwargs) # 调用父类的 execute 方法执行被装饰的函数。
_end = time.perf_counter() # 记录结束时间。
self.time = _end - _start # 计算并存储执行时间。
return result # 返回被装饰函数的结果。

示例用法

下面是如何使用 Timer 类来装饰一个函数,并测量其执行时间的示例:

import time
from adorner import Timer timer = Timer() # 可装饰多个函数,不过不太推荐(多个函数先后执行会覆盖掉计时器的元数据) @timer
def my_function(a, b):
"""一个简单的函数,用于演示 Timer 装饰器的使用。"""
time.sleep(1) # 模拟一个耗时操作。
return a + b result = my_function(1, 2)
print(f'Execution result: {result}')
print(f"Execution time: {timer.time} seconds")

输出将类似于:

Execution result: 3
Execution time: 1.0067455 seconds

Cacher

Cacher 类是一个装饰器类,用于管理和缓存函数对象及其相关数据,函数不仅仅是函数,本身也是轻量级的缓存器。

源码注释

class Cacher:
hash = dict() # 用于存储每个被装饰函数的 Cacher 实例。 def __new__(cls, function):
"""
确保每个被装饰的函数只有一个 Cacher 实例。
如果该函数已经有一个 Cacher 实例,则返回该实例;
否则,创建一个新的实例,并将其存储在 hash 中。
"""
if function in cls.hash:
instance = cls.hash[function]
else:
instance = object.__new__(cls)
instance.function = function # 设置缓存实例对应的函数
instance.data = dict() # 缓存存储的结构是字典
setattr(instance, '__name__', f'{cls.__name__}-{function.__name__}')
cls.hash[function] = instance return instance def __call__(self, *args, **kwargs):
"""
使 Cacher 实例可以像函数一样被调用。
调用被装饰的函数,并返回其结果。
"""
return self.function(*args, **kwargs) def __repr__(self):
"""
返回对象的字符串表示形式,便于调试和查看对象信息。
"""
return f'<{self.__class__.__name__}: {self.function.__name__}>' def __iter__(self):
"""
使 Cacher 实例可迭代,迭代缓存数据。
"""
return iter(self.data) def __contains__(self, item):
"""
判断缓存数据中是否包含指定的键。
"""
return item in self.data def __add__(self, other):
"""
支持使用 + 运算符合并缓存数据。
"""
if isinstance(other, self.__class__):
self.data.update(other.data)
return self
if isinstance(other, dict):
self.data.update(other)
return self
if isinstance(other, (tuple, list)):
self.data.update(dict(other))
return self
raise TypeError(f'unsupported operand type(s) for +: \'{type(self)}\' and \'{type(other)}\'') def __sub__(self, other):
"""
支持使用 - 运算符从缓存数据中删除指定的键。
"""
if isinstance(other, self.__class__):
for key in other.data:
self.data.pop(key, None)
return self
if isinstance(other, dict):
for key in other:
self.data.pop(key, None)
return self
if isinstance(other, (tuple, list)):
self.pops(*other)
return self
raise TypeError(f'unsupported operand type(s) for -: \'{type(self)}\' and \'{type(other)}\'') def items(self):
return self.data.items() def set(self, key, value, safe=False):
"""
设置缓存数据。
如果 safe 为 True,则只有在 key 不存在的情况下才设置值。
"""
if not safe:
self.data[key] = value
elif key not in self.data:
self.data[key] = value return self.data[key] def sets(self, **data_dict):
"""
批量设置缓存数据。
"""
self.data.update(data_dict) def get(self, key, default_value=None):
"""
获取缓存数据。
如果 key 不存在,则返回 default_value。
"""
return self.data.get(key, default_value) @staticmethod
def _apply_filter(values, filter_function, filter_safe, filter_errors):
"""应用筛选函数"""
def safe_filter(value):
try:
return filter_function(value)
except filter_errors:
return False filter_func = safe_filter if filter_safe else filter_function
return {key: value for key, value in values.items() if filter_func(value)} @staticmethod
def _apply_map(values, map_function, map_safe, map_errors):
"""应用遍历处理的函数"""
def safe_map(value_):
try:
return True, map_function(value_)
except map_errors:
return False, None if map_safe:
new_values = {}
for key, value in values.items():
success, mapped_value = safe_map(value)
if success:
new_values[key] = mapped_value
return new_values
else:
return {key: map_function(value) for key, value in values.items()} def gets(self, *keys, default_value=None, filter_function=None, map_function=None):
"""
批量获取缓存数据。
支持通过 filter_function 过滤值,通过 map_function 处理值。
"""
values = {key: self.data.get(key, default_value) for key in keys} if filter_function:
filter_errors = filter_errors or (TypeError, ValueError, KeyError, IndexError)
values = self._apply_filter(values, filter_function, filter_safe, filter_errors) if map_function:
map_errors = map_errors or (TypeError, ValueError, KeyError, IndexError)
values = self._apply_map(values, map_function, map_safe, map_errors) return values def pop(self, key, default_value=None):
"""
删除并返回缓存数据中的指定键。
如果键不存在,则返回 default_value。
"""
return self.data.pop(key, default_value) def pops(self, *keys, default_value=None):
"""
批量删除并返回缓存数据中的指定键。
如果键不存在,则返回 default_value。
"""
return [self.data.pop(key, default_value) for key in keys]

使用案例

下面是如何使用 Cacher 类来装饰函数,并操作缓存数据的示例:

from adorner import Cacher

@Cacher
def example1(x):
"""计算乘积"""
return x * x @Cacher
def example2(x):
"""计算和"""
return x + x print(example1) # 打印:<Cacher: example>
# 正常调用
print(example1(4)) # 打印:16
# 打印函数的文档字符串
print(example1.function_doc) # 缓存设置数据
example1.set('a', 1)
example1.set('b', 2)
example1.set('c', 3) # example2.set('a', True)
# example2.set('b', False)
# 和上述一致
example2.sets(a=True, b=False, d='数据 d') # 获取缓存数据
print(example1.get('a'))
print(example1.get('d', '数据不存在'))
# 检查 d 是否在缓存器 example1 中
print('d' in example1) # 缓存数据合并
new_cacher = example1 + example2
print(new_cacher.data) # 缓存器的所有数据
# 打印:{'a': True, 'b': False, 'c': 3, 'd': '数据 d'} print(list(new_cacher)) # 将缓存器转为列表,可呈现存储的键 new_cacher += {'e': '合并的数据 e'}
# 迭代打印
for k, v in new_cacher.items():
print(k, v) # 批量获取数据
print(new_cacher.gets('a', 'b', 'z', default_value='没有这个数据'))
print(new_cacher.gets('a', 'b', 'c', filter_function=lambda x: x > 1))
# 如果比较类型不一致,可能会发生错误,比如下面这个例子:
# print(new_cacher.gets('a', 'b', 'c', 'd', filter_function=lambda x: x > 1))
# 解决方式:你可以自行捕捉,但是那样会很繁琐,推荐使用 filter_safe 参数
print(new_cacher.gets('a', 'b', 'c', 'd', filter_function=lambda x: x > 1, filter_safe=True))
# 如果启用了 filter_safe 参数还无法正常捕捉,请使用 filter_errors 指定异常,默认是 (TypeError, ValueError, KeyError, IndexError)
print(new_cacher.gets('a', 'b', 'c', 'd', filter_function=lambda x: x(),
filter_safe=True, filter_errors=(TypeError, ValueError, KeyError, IndexError))) # 除了上述的 filter_function 参数,另外还有 map_function,同理也有 map_safe 以及 map_errors 参数
print(new_cacher.gets('a', 'b', 'c', map_function=lambda x: x > 1))
print(new_cacher.gets('a', 'b', 'c', 'd', map_function=lambda x: x > 1, map_safe=True))
print(new_cacher.gets('a', 'b', 'c', 'd', map_function=lambda x: x > 1, map_safe=True, map_errors=(TypeError,))) # xxx_safe 参数的功能是当传入的函数执行发生异常时对应的一个处理,当出现异常时,该值对应的键值都不应存在于结果中
# 优先级别:正常获取值 -> filter筛选 -> map遍历处理 -> 返回结果 # 弹出某个值
print(new_cacher.pop('c'))
print(new_cacher.pop('c', default_value=None)) # 上面弹出了,这里尝试弹出一个不存在的,将返回 default_value(默认None)
print(new_cacher.pop('c') == new_cacher.pop('c', default_value=None))
print(new_cacher.data) # {'a': True, 'b': False, 'd': '数据 d', 'e': '合并的数据 e'} # 批量弹出
print(new_cacher.pops('b', 'c', default_value='不存在'))
print(new_cacher.data) # {'a': True, 'd': '数据 d', 'e': '合并的数据 e'} # 减法删除
sub = new_cacher - [] # 支持减去 字典 {'a', 任意值} 以及元组 ('a',)
print(sub.data) # {'d': '数据 d', 'e': '合并的数据 e'}
print(new_cacher.data) # {'d': '数据 d', 'e': '合并的数据 e'}

Retryer

Retryer 类是一个装饰器类,用于在指定异常发生时重试被装饰函数的执行。它允许设置最大重试次数、重试间隔时间以及需要捕获的异常类型。该类为函数添加了自动重试机制,适用于网络请求、文件操作等可能会临时失败的操作。

源码注释

from typing import Union, List, Type
import time class Retryer:
def __init__(self, max_retry: Union[int] = 3, delay: Union[int] = 0, catches: List[Type[Exception]] = None):
"""
初始化 Retryer 实例。 :param max_retry: 最大重试次数,默认为 3。
:param delay: 每次重试之间的延迟时间(秒),默认为 0。
:param catches: 需要捕获的异常类型列表,默认为空列表。
"""
self.max_retry = max_retry
self.delay = delay
self.catches = catches or []
self.exceptions = []
self.count = 0 def __call__(self, function):
"""使 Retryer 实例可作为装饰器使用。"""
return Decorator(self.run)(function) def run(self, decorator: Decorator):
"""执行重试逻辑。"""
_catch_exceptions = tuple(self.catches) if self.catches else Exception
self.exceptions.clear()
i = 0
while i <= self.max_retry:
self.count = i
try:
result = decorator.execute()
except _catch_exceptions as e:
self.exceptions.append(e)
i += 1
if i <= self.max_retry:
time.sleep(self.delay)
continue
else:
return result
raise self.exceptions[-1]

示例用法

下面是如何使用 Retryer 类来装饰一个函数,并在指定异常发生时重试的示例:

import random
from adorner import Retryer # 创建 Retryer 实例,设置捕获的异常类型为 KeyError,当被装饰的函数中出现该错误时将进行重试
retryer = Retryer(catches=[KeyError]) @retryer
def unreliable_function():
"""一个可能会抛出异常的函数,用于演示 Retryer 装饰器的使用"""
option = random.randint(0, 2)
if option == 0:
raise KeyError('Random KeyError')
elif option == 1:
raise ValueError('Random ValueError')
else:
return "Success" try:
result = unreliable_function()
print(result)
except Exception as e:
print(f"Function failed after retries: {e}") # 打印重试次数和捕获的异常
print(f"Retry count: {retryer.count}")
print(f"Exceptions: {retryer.exceptions}")

输出将类似于:

Success
Retry count: 0
Exceptions: []

或在发生异常时:

Function failed after retries: Random KeyError
Retry count: 3
Exceptions: [KeyError('Random KeyError'), KeyError('Random KeyError'), KeyError('Random KeyError')]

adorner 使用示例的更多相关文章

  1. [WPF系列]-Adorner

      简介 通常我们想对现有的控件,做些修饰时我们就会想到一个装饰模式.WPF中也提供了这样的实现思路:通过将Adorner添加到AdornerLayer中来实现装饰现有控件的效果.如图示:   本来T ...

  2. Swift3.0服务端开发(一) 完整示例概述及Perfect环境搭建与配置(服务端+iOS端)

    本篇博客算是一个开头,接下来会持续更新使用Swift3.0开发服务端相关的博客.当然,我们使用目前使用Swift开发服务端较为成熟的框架Perfect来实现.Perfect框架是加拿大一个创业团队开发 ...

  3. .NET跨平台之旅:将示例站点升级至 ASP.NET Core 1.1

    微软今天在 Connect(); // 2016 上发布了 .NET Core 1.1 ,ASP.NET Core 1.1 以及 Entity Framework Core 1.1.紧跟这次发布,我们 ...

  4. 通过Jexus 部署 dotnetcore版本MusicStore 示例程序

    ASPNET Music Store application 是一个展示最新的.NET 平台(包括.NET Core/Mono等)上使用MVC 和Entity Framework的示例程序,本文将展示 ...

  5. WCF学习之旅—第三个示例之四(三十)

           上接WCF学习之旅—第三个示例之一(二十七)               WCF学习之旅—第三个示例之二(二十八)              WCF学习之旅—第三个示例之三(二十九)   ...

  6. JavaScript学习笔记(一)——延迟对象、跨域、模板引擎、弹出层、AJAX示例

    一.AJAX示例 AJAX全称为“Asynchronous JavaScript And XML”(异步JavaScript和XML) 是指一种创建交互式网页应用的开发技术.改善用户体验,实现无刷新效 ...

  7. XAMARIN ANDROID 二维码扫描示例

    现在二维码的应用越来越普及,二维码扫描也成为手机应用程序的必备功能了.本文将基于 Xamarin.Android 平台使用 ZXing.Net.Mobile  做一个简单的 Android 条码扫描示 ...

  8. iOS之ProtocolBuffer搭建和示例demo

    这次搭建iOS的ProtocolBuffer编译器和把*.proto源文件编译成*.pbobjc.h 和 *.pbobjc.m文件时,碰到不少问题! 搭建pb编译器到时没有什么问题,只是在把*.pro ...

  9. Android种使用Notification实现通知管理以及自定义通知栏(Notification示例四)

    示例一:实现通知栏管理 当针对相同类型的事件多次发出通知,作为开发者,应该避免使用全新的通知,这时就应该考虑更新之前通知栏的一些值来达到提醒用户的目的.例如我们手机的短信系统,当不断有新消息传来时,我 ...

  10. oracle常用函数及示例

    学习oracle也有一段时间了,发现oracle中的函数好多,对于做后台的程序猿来说,大把大把的时间还要学习很多其他的新东西,再把这些函数也都记住是不太现实的,所以总结了一下oracle中的一些常用函 ...

随机推荐

  1. java学习之旅(day.08)

    类与对象的关系 类是一种抽象的数据类型,是对某一类事物的描述,但并不代表具体的事物,如动物与狗的关系,类描述的是某一类事物具备的共同特点 对象是抽象概念的具体实例 能够展现出功能,体现出特点的是具体的 ...

  2. centos 7网卡配置文件详解(ifcfg-ens33)

    centos 7网卡配置文件详解(ifcfg-ens33) [root@xuegod63 ~]# cat /etc/sysconfig/network-scripts/ifcfg-ens33 TYPE ...

  3. linux file命令查看文件类型

    在linux系统中,linux是不根据后缀名识别文件类型的,所以使用file命令查看文件的类型. [root@node5 ~]# file /etc/shadow /etc/shadow: ASCII ...

  4. kubelet gc 源码分析

    代码 kubernetes 1.26.15 问题 混部机子批量节点NotReady(十几个,丫的重大故障),报错为: 意思就是 rpc 超了,节点下有太多 PodSandBox,crictl ps - ...

  5. 解决老旧电脑在win7中浏览器访问https网站出现的Let‘sEncrypt证书过期的问题

    原因LetsEncrypt证书未过期,但是其顶级ca根证书 "DST Root CA X3"在2021-09-01过期了,老旧设备上的win系统会被影响到. 解决步骤下载三张Let ...

  6. iNeuOS工业互联网操作系统,增加电力IEC104协议

    1.      概述... 2 2.      配置IEC104协议设备驱动... 2 1.   概述 IEC60870-5-104 是一种电力自动化系统中常用的通信协议,使用 TCP/IP 协议作为 ...

  7. Dijkstra(迪杰斯特拉)算法

    Dijkstra是什么算法 Dijkstra是典型最短路径算法,用于计算一个节点到其他节点的最短路径.该算法使用的是贪心策略:每次都找出剩余顶点中与源点距离最近的一个顶点. 什么是最短路径问题  给定 ...

  8. Java中编译异常与运行异常的区别

    编译期异常和运行期异常的区别 编译期异常和运行期异常的区别如下 异常处理要求不同:编译期异常(也称为检测异常checked Exception)要求在代码中显式地处理(使用try-catch或者thr ...

  9. 【简写Mybatis-02】注册机的实现以及SqlSession处理

    前言 注意: 学习源码一定一定不要太关注代码的编写,而是注意代码实现思想: 通过设问方式来体现代码中的思想:方法:5W+1H 源代码: https://gitee.com/xbhog/mybatis- ...

  10. Easysearch 压缩功能的显著提升:从 8.7GB 到 1.4GB

    引言 在海量数据的存储和处理中,索引膨胀率是一个不可忽视的关键指标.它直接影响了存储成本和查询性能.近期,Easysearch 在这方面取得了显著的进展,其压缩功能的效果远超过了之前的版本.本文将详细 ...