一些装饰器,可以减少重复编写。比较常用的。
用的时候函数上面加上装饰器就可以。
import sys import traceback from functools import wraps import time import unittest from app.apis.fliggy.utils import LogManager from tomorrow3 import threads as tomorrow_threads handle_exception_log = LogManager('function_error').get_logger_and_add_handlers() run_times_log = LogManager('run_many_times').get_logger_and_add_handlers() class CustomException(Exception): def __init__(self, err=''): err0 = 'fatal exception\n' Exception.__init__(self, err0 + err) def run_many_times(times=1): """把函数运行times次的装饰器 :param times:运行次数""" def _run_many_times(func): @wraps(func) def __run_many_times(*args, **kwargs): for i in range(times): LogManager('run_many_times').get_logger_without_handlers().debug('* ' * 50 + '当前是第 {} 次运行[ {} ]函数'.format(i + 1, func.__name__)) func(*args, **kwargs) return __run_many_times return _run_many_times def handle_exception(retry_times=0, error_detail_level=0): """捕获函数错误的装饰器,重试并打印日志 :param retry_times : 重试次数 :param error_detail_level :为0打印exception提示,为1打印3层深度的错误堆栈,为2打印所有深度层次的错误堆栈 :type error_detail_level: int """ if error_detail_level not in [0, 1, 2]: raise Exception('error_detail_level参数必须设置为0 、1 、2') def _handle_exception(func): @wraps(func) def __handle_exception(*args, **keyargs): for i in range(retry_times + 1): try: result = func(*args, **keyargs) if i: LogManager('function_error').get_logger_without_handlers().debug( u'%s\n调用成功,调用方法--> [ %s ] 第 %s 次重试成功' % ('# ' * 40, func.__name__, i)) return result except Exception as e: if error_detail_level == 0: error_info = str(e) elif error_detail_level == 1: error_info = traceback.format_exc(limit=3) elif error_detail_level == 2: error_info = traceback.format_exc(limit=None) if i: LogManager('function_error').get_logger_without_handlers().error( u'%s\n记录错误日志,调用方法--> [ %s ] 第 %s 次错误重试,错误原因是: %s\n' % ('- ' * 40, func.__name__, i, error_info)) return __handle_exception return _handle_exception def keep_circulating(time_sleep=0.001): """间隔一段时间,一直循环运行某个方法的装饰器 :param time_sleep :循环的间隔时间 """ if not hasattr(keep_circulating, 'keep_circulating_log'): keep_circulating.log = LogManager('keep_circulating').get_logger_and_add_handlers() def _keep_circulating(func): @wraps(func) def __keep_circulating(*args, **kwargs): while 1: time.sleep(time_sleep) try: func(*args, **kwargs) except Exception as e: msg = func.__name__ + ' 运行出错\n ' + traceback.format_exc(limit=2) keep_circulating.log.error(msg) return __keep_circulating return _keep_circulating def singleton(cls): """单例模式装饰器 """ _instance = {} @wraps(cls) def _singleton(*args, **kwargs): if cls not in _instance: _instance[cls] = cls(*args, **kwargs) return _instance[cls] return _singleton def timer(func): """计时器装饰器,只能用来计算函数运行时间""" if not hasattr(timer, 'log'): timer.log = LogManager('timer').get_logger_and_add_handlers() @wraps(func) def _timer(*args, **kwargs): t1 = time.time() result = func(*args, **kwargs) t2 = time.time() t_spend = t2 - t1 timer.log.debug('执行[ {} ]方法用时 {} 秒'.format(func.__name__, t_spend)) return result return _timer class TimerContext(object): """ 用上下文管理器计时,可对代码片段计时 """ log = LogManager('TimerContext').get_logger_and_add_handlers() def __enter__(self): self.t1 = time.time() def __exit__(self, exc_type, exc_val, exc_tb): t_spend = time.time() - self.t1 self.log.debug('执行代码片段用时 {} 秒'.format(t_spend)) def where_is_it_called(func): """一个装饰器,被装饰的函数,如果被调用,将记录一条日志,记录函数被什么文件的哪一行代码所调用""" if not hasattr(where_is_it_called, 'log'): where_is_it_called.log = LogManager('where_is_it_called').get_logger_and_add_handlers() @wraps(func) def _where_is_it_called(*args, **kwargs): # 获取被调用函数名称 # func_name = sys._getframe().f_code.co_name func_name = func.__name__ # 获取被调用函数在被调用时所处代码行数 line = sys._getframe().f_back.f_lineno # 获取被调用函数所在模块文件名 file_name = sys._getframe().f_code.co_filename where_is_it_called.log.debug('模块 [{}] 中的方法 [{}] 正在被文件 [{}] 的第 [{}] 行调用'.format(func.__module__, func_name, file_name, line)) result = func(*args, **kwargs) return result return _where_is_it_called class _Test(unittest.TestCase): @unittest.skip def test_superposition(self): """测试多次运行和异常重试,测试装饰器叠加""" @run_many_times(3) @handle_exception(2, 1) def f(): import json json.loadxxxxxx f() @unittest.skip def test_handle_exception(self): """测试异常重试装饰器""" import requests @handle_exception(2, 0) def f3(): pass requests.get('dsdsdsd') f3() @unittest.skip def test_run_many_times(self): """测试运行5次""" @run_many_times(5) def f1(): print('hello') time.sleep(1) f1() @unittest.skip def test_tomorrow_threads(self): """测试多线程装饰器,每2秒打印5次""" @tomorrow_threads(5) def f2(): print(time.strftime('%H:%M:%S')) time.sleep(2) [f2() for _ in range(9)] @unittest.skip def test_singleton(self): """测试单例模式的装饰器""" @singleton class A(): def __init__(self, x): self.x = x a1 = A(3) a2 = A(4) self.assertEqual(id(a1), id(a2)) print(a1.x, a2.x) @unittest.skip def test_keep_circulating(self): """测试间隔时间,循环运行""" @keep_circulating(3) def f6(): print("每隔3秒,一直打印 " + time.strftime('%H:%M:%S')) f6() @unittest.skip def test_timer(self): """测试计时器装饰器""" @timer def f7(): time.sleep(2) f7() @unittest.skip def test_timer_context(self): """ 测试上下文,对代码片段进行计时,获取代码所在位置 """ with TimerContext() as timer_context: time.sleep(2) def test_where_is_it_called(self): """测试函数被调用的装饰器,被调用2次将会记录2次被调用的日志""" @where_is_it_called def f9(a, b): result = a + b print(result) time.sleep(2) return result f9(1, 2) f9(3, 4) if __name__ == '__main__': unittest.main()
都是常规的,有带参数和不带参数的装饰器。其中where_is_it_called装饰器比较复杂一点,是记录哪个模块的哪个函数在哪个时候被哪个文件的哪一行代码调用过。