我应该如何捕获可以引发的装饰器函数中的异常,如果装饰器函数在库中我无法修改?

时间:2015-05-07 19:07:34

标签: python sockets google-app-engine decorator statsd

我在Google App Engine(GAE)上使用python statsd library。不幸的是,GAE可能会在使用套接字时不时提升ApplicationError: 4 Unknown error.。错误是apiproxy_errors.ApplicationError

statsd客户端已经设置为捕获socket.error,但不是套接字可以在GAE上引发的ApplicationError

我专门使用timer,它返回Timer的实例: https://github.com/jsocol/pystatsd/blob/master/statsd/client.py#L13

__call__的{​​{1}}方法允许它用作装饰器,如下所示:

from statsd import StatsClient

statsd = StatsClient()

@statsd.timer('myfunc')
def myfunc(a, b):
    """Calculate the most complicated thing a and b can do."""

我没有简单的能力来修改Timer方法本身,只是为了捕捉Timer.__call__

我应该如何编写一个包装器或其他装饰器,它仍然允许像ApplicationError这样的干净装饰,但是它会捕获包装/装饰@my_timer_wrapper('statsd_timer_name')方法中可能出现的其他异常?

这是我的代码库中的基础模块,将在许多地方使用(无论我们想要什么时间)。因此虽然this SO answer可能有用,但我真的希望避免强迫我的代码库中timer的所有用法都在@statsclient.timer块中定义。

我正在考虑做以下事情:

def my_timer_wrapper(wrapped_func, *args, **kwargs):
  @functools.wraps(wrapped_func)
  class Wat(object):
    def __call__(self, *args, **kwargs):
      timer_instance = stats_client.timer(*args, **kwargs)
      try:
        return timer_instance.__call__(wrapped_func)(*args, **kwargs)
      except Exception:
        logger.warning("Caught exception", exc_info=True)
        def foo():
          pass
        return foo

  return Wat()

然后将被用作:

@my_timer_wrapper('stastd_timer_name')
def timed_func():
  do_work()

是否有更好或更多的pythonic方式?

1 个答案:

答案 0 :(得分:1)

看起来像是“尽可能直截了当” 新的装饰者在你的计时器装饰器周围添加额外的尝试/除外。

唯一的问题是需要参数的装饰器 要定义的2级嵌套函数,几乎总是会产生它们 看起来很复杂,即使他们不是:

from functools import wraps

def  shielded_timer(statsd, 
                    exceptions=(apiproxy_errors.ApplicationError),
                    name=None):

    def decorator(func):
        timer_decorator = statsd.timer(name or func.__name__)
        new_func = timer_decorator(func)
        @wraps(func)
        def wrapper(*args, **kw):
            try:
                return new_func(*args, **kw)
            except BaseException as error:
                if isinstance (error, exceptions):
                    # Expected error (ApplicationError by default) ocurred
                    pass
                else:
                    raise
        return wrapper
    return decorator



#####################
statsd = StatsClient()
@shielded_timer(statsd)
def my_func(a,b):
    ...

正如你所看到的那样,即使包含额外的细节也很容易 - 在这种情况下,我已经在装饰时可以配置所需的例外,并且可选地,它自动使用装饰函数的名称。 请致电statsd.timer。