简体   繁体   English

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

[英]Determining the number of return values in a Python function

I am creating a decorator that catches a raised error in it's target function, and allows the user to continue executing the script (bypassing the function) or drop out of the script. 我正在创建一个装饰器,它在其目标函数中捕获一个引发错误,并允许用户继续执行脚本(绕过该函数)或退出脚本。

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

Notice that, if the user chooses to bypass the error-returning function and continue executing the script, the decorator returns None. 请注意,如果用户选择绕过错误返回功能并继续执行脚本,则装饰器将返回None。 This works well for functions that only return a single value, but it is crashing on functions that attempt to unpack multiple values. 这适用于仅返回单个值的函数,但它会在尝试解压缩多个值的函数上崩溃。 For instance, 例如,

# 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()

Is there a way that I can determine the number of values my target function is supposed to return? 有没有办法可以确定我的目标函数应该返回的值的数量? For instance, something like: 例如,类似于:

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

As always, if there is a better way to do this sort of thing, please let me know. 和往常一样,如果有更好的方法来做这种事情,请告诉我。 Thanks! 谢谢!

UPDATE: 更新:

Thanks for all the suggestions. 感谢所有的建议。 I decided to narrow the scope of catch_error to a single class of functions, which only return one string value. 我决定将catch_error的范围缩小到单个类函数,它只返回一个字符串值。 I just split all the functions returning more than one value into separate functions that return a single value to make them compatible. 我只是将返回多个值的所有函数拆分为单独的函数,这些函数返回单个值以使它们兼容。 I had been hoping to make catch_error more generic (and there were several helpful suggestions on how to do that), but for my application it was a little overkill. 我一直希望让catch_error更通用(并且有一些有用的建议如何做到这一点),但对于我的应用程序来说,它有点矫枉过正。 Thanks again. 再次感谢。

No, there is no way you can determine that. 不,你无法确定这一点。

Python is a dynamic language, and a given function can return an arbitrary sequence of any size or no sequence at all, based on the inputs and other factors. Python是一种动态语言,给定的函数可以根据输入和其他因素返回任意大小的任意序列或根本不返回任何序列。

For example: 例如:

import random

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

This function will return a tuple half of the time, but None the other half, so Python has no way of telling you what foo() will return. 这个函数将返回一半的元组,但是None另一半,所以Python无法告诉你foo()将返回什么。

There are many more ways your decorator can fail, btw, not just when the function returns a sequence that the caller then unpacks. 你的装饰器还有很多方法可以失败,顺便说一句,不仅仅是当函数返回一个调用者解压缩的序列时。 What if the caller expected a dictionary and starts trying to access keys, or a list and wants to know the length? 如果调用者期望字典并开始尝试访问密钥或列表并想知道长度,该怎么办?

Your decorator can't predict what your function is going to return, but nothing prevents you from telling the decorator what return signature to simulate: 你的装饰者无法预测你的函数将返回什么,但没有什么能阻止你告诉装饰者要模拟的返回签名:

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

Instead of returning None , your decorator will return its argument ( [None, None] ). 您的装饰器将返回其参数( [None, None] ),而不是返回None

Writing an argument-taking decorator is just slightly tricky: The expression catch_error([None, None]) will be evaluated, and must return the actual decorator that will be applied to the decorated function. 编写一个带参数的装饰器有点棘手:表达式catch_error([None, None])将被计算,并且必须返回将应用于装饰函数的实际装饰器。 It looks like this: 它看起来像这样:

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

Note that even if you just want it to return None , you need to execute it once: 请注意,即使您只是希望它返回None ,您也需要执行一次:

@catch_error()
def some_function(x):
    ...

Martijn Pieters answer is correct, this is a specific case of the Halting Problem Martijn Pieters的回答是正确的,这是停机问题的一个具体案例

However you might get around it by passing a error return value to the decorator. 但是,您可以通过将错误返回值传递给装饰器来解决它。 Something like this: 像这样的东西:

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

Then you could decorate using: 然后你可以使用以下装饰:

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

Also as a point of style, if you are grabbing *args in your wrapped function, you should probably also grab **kwargs so that you can properly wrap functions that take keyword arguments. 另外作为一种风格,如果你在包装函数中抓取*args ,你应该也可以获取**kwargs这样你就可以正确地包装带有关键字参数的函数。 Otherwise your wrapped function will fail if you call wrapped_function(something=value) 否则,如果调用wrapped_function(something=value)则包装函数将失败

Finally, as another point of style, it is confusing to see code that does if a: pass with an else. 最后,作为另一种风格, if a: pass一个else,那么看到代码会很困惑。 Try using if !a in these cases. 在这些情况下尝试使用if !a So the final code: 所以最终的代码:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM