尝试/除了班级中的每个方法?

时间:2014-06-03 21:09:56

标签: python

我目前正在使用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,然后在它周围创建一个包装器,用这个try除了包装器重新定义类中的每个方法,这样我得到一个新的对象p,当它得到500错误时自动重试

3 个答案:

答案 0 :(得分:8)

装饰者是完美的。您可以使用类似这样的装饰器来装饰每个相关方法:

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

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): ...

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

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)

答案 1 :(得分: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)

答案 2 :(得分:1)

这里的想法来自@ shx2的答案,但由于我真正想要的是一种将某些东西应用于对象中的每个函数而不是类的方法,我正在为任何人提供这个未来同样的问题:

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)