关于python:如果decorator函数在一个我不能修改的库中,我应该如何捕获可以引发的decorator函数中的异常?

How should I catch exceptions in a decorator function that can raise, if the decorator function is in a library I can't modify?

我正在谷歌应用引擎(GAE)上运行python statsd库。不幸的是,在使用套接字时,GAE可以不时地提高ApplicationError: 4 Unknown error.。错误是一个apiproxy_errors.ApplicationError

statsd客户机已经设置为捕获socket.error,但不是套接字可以在gae上提升的ApplicationError

我专门与timer合作,它返回timer的一个实例:https://github.com/jsocol/py statsd/blob/master/statsd/client.py l13

timer__call__方法允许它用作装饰,如下所示:

1
2
3
4
5
6
7
from statsd import StatsClient

statsd = StatsClient()

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

我没有简单的能力修改Timer.__call__方法本身来简单地捕获ApplicationError

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

这是我的代码库中的一个基础模块,它将被用于很多地方(无论我们想在什么地方使用时间)。因此,尽管这个答案可能有效,但我真的希望避免在我的代码库中强制所有@statsclient.timer的使用都在try-except块中定义。

我想做如下的事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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()

然后使用如下:

1
2
3
@my_timer_wrapper('stastd_timer_name')
def timed_func():
  do_work()

有更好或更多的Python疗法吗?


看起来这是一个"尽可能简单"的例子。新的装饰添加一个额外的尝试/除了围绕你的计时器装饰。

唯一的问题是,采用参数的修饰符需要要定义的两级嵌套函数,几乎总是使它们看起来很复杂,即使它们不是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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。