从 python 函数分配可变数量 returns 的优雅方式

Elegant way to assign a variable number of returns from a python function

我正在使用一个 python 库,它的函数以列表形式返回结果。根据实验配置,此列表由一个或两个元素组成。在这种情况下,分配函数输出的直接方法如下:

bbox, segm = None, None
results = test_model()  # returns a list with either one or two elements
bbox = results[0]
if len(results) > 1:
    segm = results[1]

# [some other code here]
if segm is not None:
    plot(segm)

然而,这似乎很冗长,因为我们需要先将 bboxsegm 都初始化为 None,然后计算 if len(results) > 1。有什么 pythonic 方法可以避免这种情况吗?理想情况下,像这样的东西会非常好:

bbox, segm = test_model()  # returns a list with either one or two elements
if segm is not None:
    plot(segm)

您可以使用 structural pattern matching,在 Python 3.10 中可用:

match test_model():
   case [bbox, segm]:
      plot(segm)
   case bbox:
      pass

但是,如果test_module总是returns一个元组,你可以使用解包:

bbox, *segm = test_model()

现在,如果 text_model 返回一个值,segm 将是一个空列表 ([])。但是,如果它返回两个值,segm 将包含一个元素。因此,您的完整代码可以变成:

bbox, *segm = test_model()
if segm:
   plot(segm[0])

您可以扩展 return list 一个额外的元素,然后切掉 2 个需要的元素。

def foo(boolean):
    if boolean:
        return [True, True]
    return [False]

bar, baz = (foo(False) + [None])[:2]
print(bar, baz) # False None

bar, baz = (foo(True) + [None])[:2]
print(bar, baz) # True True

pythonic 方式将是 * 如 @Ajax1234 提到的那样解包,但这是另一种选择。

如果您可以控制函数定义,您也可以添加装饰器。

def returns_two(func):
    def wrapper(*args):
        return (func(*args) + [None])[:2]
    return wrapper


@returns_two
def foo(boolean):
    if boolean:
        return [True, True]
    return [False]


bar, baz = foo(False)
print(bar, baz) # False None

bar, baz = foo(True)
print(bar, baz) # True True

如果您使用的是 Python 3.10,请使用

否则,您的首要任务是编写易于阅读的内容,而不是做任何花哨的事情。像这样分配给元组会起作用:

try:
    bbox, segment = results
except ValueError:
    bbox, segment = results[0], None

这有很多优点:

  • 你一目了然。
  • 如果列表的长度不是 1 或 2,您将得到一个错误,而不是静默失败。

处理异常时,将异常用于实际异常的情况是有意义的。所以如果你希望一个列表项有更多的结果,你可以反过来写:

try:
    bbox, = results
    segment = None
except ValueError:
    bbox, segment = results

还有两种方法,如果test_model() returns只有一个值,则将None分配给segm

bbox, segm, *_ = *test_model(), None
bbox, segm = (*test_model(), None)[:2]