2

我正在创建一个装饰器,它在它的目标函数中捕获一个引发的错误,并允许用户继续执行脚本(绕过函数)或退出脚本。

def catch_error(func):
    """
    This decorator is used to make sure that if a decorated function breaks 
    in the execution of a script, the script doesn't automatically crash. 
    Instead, it gives you the choice to continue or gracefully exit.    

    """
    def caught(*args):
        try:
            return func(*args)
        except Exception as err:
            question = '\n{0} failed. Continue? (yes/no): '.format(func.func_name)
            answer = raw_input(question)
            if answer.lower() in ['yes','y']:
                pass
            else:
                print "   Aborting! Error that caused failure:\n"
                raise err 
            return None
    return caught

请注意,如果用户选择绕过错误返回函数并继续执行脚本,则装饰器将返回 None。这适用于只返回单个值的函数,但它会在尝试解包多个值的函数上崩溃。例如,

# Both function and decorator return single value, so works fine
one_val = decorator_works_for_this_func() 
# Function nominally returns two values, but decorator only one, so this breaks script
one_val, two_val = decorator_doesnt_work_for_this_func()

有没有办法可以确定我的目标函数应该返回的值的数量?例如,类似:

def better_catch_error(func):
    def caught(*args):
        try:
            return func(*args)
        except Exception as err:
            ...
            num_rvals = determine_num_rvals(func)
            if num_rvals > 1:
                return [ None for count in range(num_rvals) ]
            else:
                return None               
    return caught

与往常一样,如果有更好的方法来做这种事情,请告诉我。谢谢!

更新:

感谢所有的建议。我决定将 catch_error 的范围缩小到一个函数类,它们只返回一个字符串值。我只是将返回多个值的所有函数拆分为返回单个值的单独函数以使它们兼容。我一直希望让 catch_error 更通用(并且有一些关于如何做到这一点的有用建议),但对于我的应用程序来说这有点过头了。再次感谢。

4

3 回答 3

3

不,你无法确定这一点。

Python 是一种动态语言,给定的函数可以根据输入和其他因素返回任意大小的任意序列或根本不返回序列。

例如:

import random

def foo():
    if random.random() < 0.5:
        return ('spam', 'eggs')
    return None

这个函数一半的时间会返回一个元组,但None另一半,所以 Python 无法告诉你foo()会返回什么。

顺便说一句,你的装饰器可能会失败的方式还有很多,不仅仅是当函数返回一个调用者然后解包的序列时。如果调用者期望字典并开始尝试访问键或列表并想知道长度怎么办?

于 2013-01-30T00:01:48.753 回答
3

Martijn Pieters 的回答是正确的,这是Halting Problem的一个具体案例

但是,您可以通过将错误返回值传递给装饰器来解决它。像这样的东西:

def catch_error(err_val):
    def wrapper(func):
        def caught(*args):
            try:
                return func(*args)
            except Exception as err:
                question = '\n{0} failed. Continue? (yes/no): '.format(func.func_name)
                answer = raw_input(question)
                if answer.lower() in ['yes','y']:
                    pass
                else:
                    print "   Aborting! Error that caused failure:\n"
                    raise err 
                return err_val
        return caught
    return wrapper

然后你可以装饰使用:

@catch_error({})
def returns_a_dict(*args, **kwargs):
    return {'a': 'foo', 'b': 'bar'}

同样作为一种风格,如果您*args在包装函数中抓取,您可能还应该抓取**kwargs,以便您可以正确包装带有关键字参数的函数。否则,如果您调用,您的包装函数将失败wrapped_function(something=value)

最后,作为另一种风格,看到if a: pass使用 else 的代码是令人困惑的。尝试if !a在这些情况下使用。所以最后的代码:

def catch_error(err_val):
    def wrapper(func):
        def caught(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except Exception as err:
                question = '\n{0} failed. Continue? (yes/no): '.format(func.func_name)
                answer = raw_input(question)
                if answer.lower() not in ['yes','y']:
                    print "   Aborting! Error that caused failure:\n"
                    raise err
                return err_val
        return caught
    return wrapper
于 2013-01-30T00:40:21.457 回答
3

您的装饰器无法预测您的函数将返回什么,但没有什么能阻止您告诉装饰器要模拟什么返回签名:

@catch_error([None, None])
def tuple_returner(n):
    raise Exception
    return [2, 3]

None你的装饰器不会返回,而是返回它的参数 ( [None, None])。

编写一个接受参数的装饰器有点棘手:表达式catch_error([None, None])将被评估,并且必须返回将应用于装饰函数的实际装饰器。它看起来像这样:

def catch_error(signature=None):
    def _decorator(func):
        def caught(*args):
            try:
                return func(*args)
            except Exception as err:
                # Interactive code suppressed
                return signature

        return caught

    return _decorator

请注意,即使您只是希望它返回None,也需要执行一次:

@catch_error()
def some_function(x):
    ...
于 2013-01-30T01:09:05.753 回答