最初是
这篇文章 。 然后
对她发表了
评论 。 结果,我深入阅读了材料,深入研究了debag,并从故事的第一部分开始就优化了代码。 我建议与我同行。
首先,我要感谢
Mogost 。 感谢他的评论,我重新定义了Python的方法。 我以前曾听说在Python专家中(处理内存时)有很多不经济的人,但是现在事实证明我以某种方式无形地加入了这个聚会。
因此,让我们开始吧。 让我们推测一下总体的瓶颈。
持久如果:
if isinstance(self.custom_handlers, property): if self.custom_handlers and e.__class__ in self.custom_handlers: if e.__class__ not in self.exclude:
这不是极限。 因此,我删除了部分ifs,然后将某些内容转移到__init__,即 到将被调用一次的位置。 具体来说,检查代码中的属性应调用一次,因为 装饰器将应用于方法并分配给它。 并且该类的属性将分别保持不变。 因此,无需经常检查该属性。
如果在,则另外一个点。 探查器显示,每个此类都有一个单独的调用,因此我决定将所有处理程序合并为一个字典。 这通常可以避免ifs,而不是简单地使用:
self.handlers.get(e.__class__, Exception)(e)
因此,在self.handlers中,我们有一个dict,默认情况下包含一个引发
其他异常的函数。
当然,包装器应特别注意。 这与每次调用装饰器时调用的函数相同。 即 在这里最好避免不必要的检查,并尽可能避免各种负荷,将它们放在__init__或__call__中。 这是包装器之前的内容:
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)
支票的数目穿过屋顶。 每次对装饰器的调用都将调用此方法。 因此包装器变得像这样:
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
回想一下,__ call__将被调用一次。 在__call__内部,根据函数的异步程度,我们返回函数本身或协程。 而且我还想指出,asyncio.iscoroutinefunction进行了另一个调用,因此我切换到inspect.iscoroutinefunction。 实际上,长凳(cProfile)用于异步处理并检查:
ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.000 0.000 <string>:1(<module>) 1 0.000 0.000 0.000 0.000 coroutines.py:160(iscoroutinefunction) 1 0.000 0.000 0.000 0.000 inspect.py:158(isfunction) 1 0.000 0.000 0.000 0.000 inspect.py:179(iscoroutinefunction) 1 0.000 0.000 0.000 0.000 {built-in method builtins.exec} 1 0.000 0.000 0.000 0.000 {built-in method builtins.isinstance} 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.000 0.000 <string>:1(<module>) 1 0.000 0.000 0.000 0.000 inspect.py:158(isfunction) 1 0.000 0.000 0.000 0.000 inspect.py:179(iscoroutinefunction) 1 0.000 0.000 0.000 0.000 {built-in method builtins.exec} 1 0.000 0.000 0.000 0.000 {built-in method builtins.isinstance} 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
完整代码:
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)
如果没有时间,示例可能是不完整的。 因此,使用上面注释中的示例:
class MathWithTry(object): def divide(self, a, b): try: return a // b except ZeroDivisionError: return ' , '
以及
上一篇文章的示例(
注意!我们将
e传递给lambda文本中的示例。在上一篇文章中不是这种情况,仅在创新中添加了该示例):
class Math(object): @property def exception_handlers(self): return { ZeroDivisionError: lambda e: ' , ' } @ProcessException(exception_handlers) def divide(self, a, b): return a // b
这是给您的结果:
timeit.timeit('math_with_try.divide(1, 0)', number=100000, setup='from __main__ import math_with_try') 0.05079065300014918 timeit.timeit('math_with_decorator.divide(1, 0)', number=100000, setup='from __main__ import math_with_decorator') 0.16211646200099494
总而言之,我想说的是,我认为优化是一个相当复杂的过程,在这里重要的是不要迷失方向或不优化某些东西,以免影响可读性。 否则,优化时的借记将非常困难。
感谢您的评论。 我也期待对这篇文章发表评论:)
PS感谢哈勃(Habr)用户的评论,它有可能进一步提高速度,事实就是这样:
from inspect import iscoroutinefunction from asyncio import QueueEmpty, QueueFull from concurrent.futures import TimeoutError class ProcessException(object): __slots__ = ('handlers',) def __init__(self, custom_handlers=None): if isinstance(custom_handlers, property): custom_handlers = custom_handlers.__get__(self, self.__class__) raise_exception = ProcessException.raise_exception 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): handlers = self.handlers if iscoroutinefunction(func): async def wrapper(*args, **kwargs): try: return await func(*args, **kwargs) except Exception as e: return handlers.get(e.__class__, handlers[Exception])(e) else: def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except Exception as e: return handlers.get(e.__class__, handlers[Exception])(e) return wrapper @staticmethod def raise_exception(e: Exception): raise e
timeit.timeit('divide(1, 0)', number=100000, setup='from __main__ import divide') 0.13714907199755544
平均加速0.03。 感谢
Kostiantyn和
Yngvie 。
PS更新! 我根据
onegreyonewhite和
resetme的注释中的注释进一步优化了代码。 用func代替self.func,并使self.handlers成为变量。 执行速度进一步加快,如果每个零的重复次数更多,则特别值得注意。 我引用timeit:
timeit.timeit('t.divide_with_decorator(1, 0)', number=1000000, setup='from __main__ import t') 1.1116105649998644
在此优化之前,使用相同数字值执行平均需要花费1.24。
PS我通过@staticmethod中的__init__引入了raise_exception函数,进一步优化了性能,我正在通过一个变量访问它,以删除通过一点的访问。 实际上,平均执行时间变为:
timeit.timeit('t.divide_with_decorator(1, 0)', number=1000000, setup='from __main__ import t') 1.0691639049982768
这是用于方法。 函数的调用速度更快(平均而言):
timeit.timeit('div(1, 0)', number=1000000, setup='from __main__ import div') 1.0463485610016505