【问题标题】:Python3. How to optimize decorator for handling exceptions?蟒蛇3。如何优化装饰器处理异常?
【发布时间】:2019-10-31 18:49:56
【问题描述】:

我有装饰器用作异常处理程序。我想优化它,因为与简单的 try...catch 相比,它慢了大约 6 倍。

我的装饰器的代码:

class ProcessException(object):

    __slots__ = ('func', 'custom_handlers', 'exclude')

    def __init__(self, custom_handlers=None):
        self.func = None
        self.custom_handlers: dict = custom_handlers
        self.exclude = [QueueEmpty, QueueFull, TimeoutError]

    def __call__(self, func):
        self.func = func
        return self.wrapper

    def wrapper(self, *args, **kwargs):
        if self.custom_handlers:
            if isinstance(self.custom_handlers, property):
                self.custom_handlers = self.custom_handlers.__get__(self, self.__class__)

        if asyncio.iscoroutinefunction(self.func):
            return self._coroutine_exception_handler(*args, **kwargs)
        else:
            return self._sync_exception_handler(*args, **kwargs)

    async def _coroutine_exception_handler(self, *args, **kwargs):
        try:
            return await self.func(*args, **kwargs)
        except Exception as e:
            if self.custom_handlers and e.__class__ in self.custom_handlers:
                return self.custom_handlers[e.__class__]()

            if e.__class__ not in self.exclude:
                raise e

    def _sync_exception_handler(self, *args, **kwargs):
        try:
            return self.func(*args, **kwargs)
        except Exception as e:
            if self.custom_handlers and e.__class__ in self.custom_handlers:
                return self.custom_handlers[e.__class__]()

            if e.__class__ not in self.exclude:
                raise e

作为基准,我将简单的函数与 try...catch 和函数与我的装饰器一起使用:

# simple function
def divide(a, b):
    try:
        return a // b
    except ZeroDivisionError:
        return 'error'

# function with decorator
@ProcessException({ZeroDivisionError: lambda: 'err'})
def divide2(a, b):
    return a // b

简单函数 10000 次迭代的结果:

timeit.timeit('divide(1, 0)', number=10000, setup='from __main__ import divide')
0.010692662000110431

以及带有装饰器的功能:

timeit.timeit('divide2(1, 0)', number=10000, setup='from __main__ import divide2')
0.053688491000002614

请帮我优化一下,请解释瓶颈在哪里?

【问题讨论】:

  • 从我的分析看来,大部分放缓似乎来自if self.custom_handlers and e.__class__ in self.custom_handlers:。如果你真的在寻找效率,写一个装饰器可能不是要走的路。
  • @rassar 感谢您的评论。你的意思是装饰器总是会变慢吗?您能否澄清一下创建异常处理程序的最佳方法是什么?
  • 嗯。仅仅通过拥有更多的线条,装饰者必须跳过更多的圈,因此需要更长的时间。此外,由于您希望能够指定异常和函数,因此解析和处理这些将花费更长的时间。您不太可能找到比您介绍的更快或更好的实现方式。

标签: python-3.x performance optimization python-decorators


【解决方案1】:

好吧,我终于优化了我的装饰器。所以,代码(解释如下):

from inspect import iscoroutinefunction

from asyncio import QueueEmpty, QueueFull
from concurrent.futures import TimeoutError


class ProcessException(object):

    __slots__ = ('func', 'handlers')

    def __init__(self, custom_handlers=None):
        self.func = None

        if isinstance(custom_handlers, property):
            custom_handlers = custom_handlers.__get__(self, self.__class__)

        def raise_exception(e: Exception):
            raise e

        exclude = {
            QueueEmpty: lambda e: None,
            QueueFull: lambda e: None,
            TimeoutError: lambda e: None
        }

        self.handlers = {
            **exclude,
            **(custom_handlers or {}),
            Exception: raise_exception
        }

    def __call__(self, func):
        self.func = func

        if iscoroutinefunction(self.func):
            def wrapper(*args, **kwargs):
                return self._coroutine_exception_handler(*args, **kwargs)
        else:
            def wrapper(*args, **kwargs):
                return self._sync_exception_handler(*args, **kwargs)

        return wrapper

    async def _coroutine_exception_handler(self, *args, **kwargs):
        try:
            return await self.func(*args, **kwargs)
        except Exception as e:
            return self.handlers.get(e.__class__, Exception)(e)

    def _sync_exception_handler(self, *args, **kwargs):
        try:
            return self.func(*args, **kwargs)
        except Exception as e:
            return self.handlers.get(e.__class__, Exception)(e)

首先,我使用 cProfile 查看装饰器进行了哪些调用。我注意到 asyncio.iscoroutinefunction 进行了额外的调用。我已经删除了它。此外,我删除了所有额外的 if 并为所有处理程序创建了通用字典。我的解决方案仍然不如 try...catch 快,但现在它的运行速度更快了。

【讨论】:

    猜你喜欢
    • 2020-01-10
    • 2020-12-03
    • 1970-01-01
    • 2016-12-14
    • 1970-01-01
    • 2017-09-20
    • 1970-01-01
    • 2021-05-12
    • 2023-03-18
    相关资源
    最近更新 更多