【问题标题】:Try/Except Every Method in Class?尝试/排除类中的每个方法?
【发布时间】:2014-06-03 21:09:56
【问题描述】:

我目前正在使用 Google 的 BigQuery API,当调用它时,偶尔会给我:

apiclient.errors.HttpError: <HttpError 500 when requesting https://www.googleapis.com/bigquery/v2/projects/some_job?alt=json returned "Unexpected. Please try again.">

返回是一件愚蠢的事情,但无论如何,当我为任何调用的方法得到这个时,我只想睡一两秒钟,然后再试一次。基本上,我想用类似的东西包装每个方法:

def new_method
    try:
        method()
    except apiclient.errors.HttpError, e:
        if e.resp.status == 500:
            sleep(2)
            new_method()
        else:
            raise e

有什么好的方法吗?

我不想显式地重新定义类中的每个方法。我只想对类中的每个方法自动应用一些东西,所以我会为以后的事情做准备。理想情况下,我会采用一个类对象 o,并围绕它创建一个包装器,该包装器使用此尝试重新定义类中的每个方法,但包装器除外,因此我得到了一些新对象 p,它在收到 500 错误时自动重试。

【问题讨论】:

标签: python


【解决方案1】:

装饰器非常适合这个。您可以使用这样的装饰器来装饰每个相关的方法:

(注意使用递归进行重试可能不是一个好主意...)

def Http500Resistant(func):
    num_retries = 5
    @functools.wraps(func)
    def wrapper(*a, **kw):
        sleep_interval = 2
        for i in range(num_retries):
            try:
                return func(*a, **kw)
            except apiclient.errors.HttpError, e:
                if e.resp.status == 500 and i < num_retries-1:
                    sleep(sleep_interval)
                    sleep_interval = min(2*sleep_interval, 60)
                else:
                    raise e    
    return wrapper

class A(object):

    @Http500Resistant
    def f1(self): ...

    @Http500Resistant
    def f2(self): ...

要将装饰器自动应用于所有方法,您可以使用另一个装饰器,这一次,装饰类:

import inspect
def decorate_all_methods(decorator):
    def apply_decorator(cls):
        for k, f in cls.__dict__.items():
            if inspect.isfunction(f):
                setattr(cls, k, decorator(f))
        return cls
    return apply_decorator

并像这样申请:

@decorate_all_methods(Http500Resistant)
class A(object):
    ...

或者喜欢:

class A(object): ...
A = decorate_all_methods(Http500Resistant)(A)

【讨论】:

  • 我在这里使用的一个技巧是增加每次失败后重试之间的间隔,以减少对已经超载的服务器的锤击。例如,在循环顶部将 retry_interval 初始化为 1.0,每次失败后将其乘以 2.0,然后休眠。
  • 我不想显式地重新定义类中的每个方法。我只想对类中的每个方法自动应用一些东西,所以我会为以后的事情做准备。理想情况下,我会采用一个类对象 o,并围绕它创建一个包装器,该包装器使用此尝试重新定义类中的每个方法,但包装器除外,因此我得到了一些新对象 p,它在收到 500 错误时自动重试。
  • @RussellBorogove,如果错误是专门关于被限制的,我会这样做,谷歌也提供,但由于这只是一个未知错误,我宁愿休眠一个静态值并重试失败前几次。
  • 有没有办法将此应用于类的实例?我正在运行一个函数,它只返回给我return discovery.build('bigquery', 'v2', http=http),我只想按照你提到的方式装饰返回的实例。
  • @Eli 一种快速而简单的方法是:obj.__class__ = Http500Resistant(obj.__class__)(仅在某些情况下有效)。但是,如果它让你的胃咕咕叫,我相信你可以写一些东西来装饰一个对象的所有方法。它与我的回答中的 decorate_all_methods 装饰器没有太大区别。
【解决方案2】:

正如其他答案指出的那样,您可以使用装饰器完成此操作:

def retry(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # retry logic
    return wrapper

如果您想自动将此装饰器应用于类的所有方法,您可以为此使用元类:

class Meta(type):
    def __new__(cls, name, bases, attrs):
        for n in attrs:
            if inspect.isfunction(attrs[n]):
                attrs[n] = retry(attrs[n])
        return super(Meta, cls).__new__(cls, name, bases, attrs)

class Api(object):
    __metaclass__ = Meta

    def function_with_retry_applied(self):
        raise HttpError(500)

【讨论】:

    【解决方案3】:

    这里的想法来自@shx2 的回答,但由于我真正想要的是一种将某些东西应用到 object 中的每个函数而不是类的方法,所以我为任何有以后同样的问题:

    def bq_methods_retry(func):
        num_retries = 5
        @functools.wraps(func)
        def wrapper(*a, **kw):
            sleep_interval = 2
            for i in xrange(num_retries):
                try:
                    return func(*a, **kw)
                except apiclient.errors.HttpError, e:
                    if e.resp.status == 500 and i < num_retries-1:
                        time.sleep(sleep_interval)
                        sleep_interval = min(2*sleep_interval, 60)
                    else:
                        raise e
        return wrapper
    
    
    def decorate_all_bq_methods(instance, decorator):
        for k, f in instance.__dict__.items():
            if inspect.ismethod(f):
                setattr(instance, k, decorator(f))
        return instance
    

    现在,当您创建一个新的 BQ 服务时,只需将 decorate_all_bq_methods() 应用于它:

    service = discovery.build('bigquery', 'v2', http=http)
    #make all the methods in the service retry when appropriate
    retrying_service = decorate_all_bq_methods(service, bq_methods_retry)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-10
      • 2013-05-28
      • 2017-03-01
      相关资源
      最近更新 更多