一些通用装饰器

一些装饰器,可以减少重复编写。比较常用的。

用的时候函数上面加上装饰器就可以。

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装饰器比较复杂一点,是记录哪个模块的哪个函数在哪个时候被哪个文件的哪一行代码调用过。

猜你喜欢

转载自www.cnblogs.com/ydf0509/p/9097181.html