Python3. Как оптимизировать декоратор для обработки исключений? - PullRequest
1 голос
/ 31 октября 2019

У меня есть декоратор, который я использую как обработчик исключений. Я хочу оптимизировать его, потому что по сравнению с простым 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

В качестве теста я использовал простую функцию спопробуй ... поймай и работай с моим декоратором:

# 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

Помогите мне, пожалуйста, оптимизировать его и, пожалуйста, объясните, где узкое место?

1 Ответ

0 голосов
/ 02 ноября 2019

Ну наконец-то я оптимизировал свой декоратор. Итак, код (пояснение ниже):

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.iscoroutine делает дополнительный вызов. Я удалил это. Также я удалил все лишние if и создал общий dict для всех обработчиков. Мое решение все еще не быстрое, просто попробуйте ... поймайте, но теперь оно работает быстрее.

...