确定Python函数中的返回值数量

2 投票
3 回答
1444 浏览
提问于 2025-04-17 14:08

我正在创建一个装饰器,它可以捕捉目标函数中抛出的错误,并允许用户继续执行脚本(跳过这个函数)或者退出脚本。

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更通用(也有一些很有帮助的建议),但对于我的应用来说,这样做有点过于复杂了。再次感谢。

3 个回答

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

不,这个是没办法确定的。

Python是一种动态语言,也就是说,某个函数根据输入和其他因素,可以返回任意大小的序列,甚至根本不返回序列。

举个例子:

import random

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

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

顺便说一下,你的装饰器可能还有很多其他失败的情况,不仅仅是当函数返回一个调用者要解包的序列时。比如说,如果调用者期待的是一个字典,然后开始尝试访问键,或者期待的是一个列表,想知道长度,这种情况也会出问题。

3

Martijn Pieters的回答是对的,这其实是一个特定的情况,属于停机问题

不过,你可以通过给装饰器传递一个错误返回值来解决这个问题。可以这样做:

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

撰写回答