Python中的nargout

28

Python有类似MATLAB中nargout的功能吗?如果我们想要保持返回参数数量的灵活性,我觉得nargout是一个非常好的方法。是否有一种方法可以知道需要请求多少输出参数?就像下面的伪代码:

def var_returns_func(a):
  """
  a is a 1D numpy array
  """
  if nargout==1: return a.sum()
  elif nargout==2: return a.sum(),a.std()

如果我用这个函数调用 mean = var_returns_func(someNumPyarray),它应该返回一个值。但是,如果我使用 mean, std = var_returns_func(someNumPyarray) 调用,它应该返回2个值。

有没有一种Pythonic的方式来实现这个? 或者说有没有巧妙的方法?

6个回答

19

函数无法知道返回值将被用于做什么,因此它也不知道需要多少个返回值。你可以将nargout作为参数传递给函数,并使用它来决定要返回什么:

def f(a, nargout=1):
    if nargout == 1:
        return "one value"
    elif nargout == 2:
        return "two", "values"
    else:
        raise ValueError, "Invalid nargout!"

这是“明确胜于含蓄”作为Python哲学的一个例子。如果你想要两个参数输出,你必须明确地说明你想要两个参数输出。在Python中,基于对未来结果的推测进行隐含决策是被反对的。在 a = f(x) 的情况下,完全可以希望在a中获得两个元素的元组。

对于像您这样的示例,有很多更好的方法。其中之一就是仅仅执行 mean, std = a.mean(), a.std(),或者更普遍的做法是 x, y = someFunc(a), otherFunc(a)。如果这些特定值的组合经常需要,并且存在一些昂贵的计算由两个操作共享,您不想重复,那么可以创建一个清楚地返回两个值的第三个函数并执行 x, y = funcThatReturnsTwoThings(a)。所有这些都是明确保持函数分离的方法,如果它们执行不同的任务。


谢谢!尽管这是我迄今为止处理此类情况的方法。理想情况下,不需要传递单独的输入参数来指定输出参数的数量。令人有点失望的是Python不支持此功能。我相信这在MATLAB中已经存在了很长时间,MATLAB中的函数定义允许指定多个输出参数。 - Nik
@Nik:“显式优于隐式”。Python 不喜欢这种魔法。(我在我的答案中添加了一点解释。) - BrenBarn
我同意显式与隐式的争论。如果这是Pythonic哲学,那么我就说服了。 - Nik
11
Matlab的nargout既不是魔术也不是隐式的,Matlab的[a, b] = f(x)语法也不类似于Python的(a, b) = f(x)。相反,Matlab中的语法[a, b] = f(x)会告诉Matlab函数f,其输出参数数量为2。这是Matlab语言中明确定义的语法规则,并且没有任何模棱两可的情况。这个Matlab特性是独特的,我不知道任何其他语言中有类似的特性。然而,我不认为这是由于Python之禅的任何规则所致。 - gerrit
Python冗余语法的另一个例子:如先前所述,Matlab语法允许解释器在调用函数时知道请求的参数数量,然后定义nargout而无需显式传递它,并定义多个输出参数以存储它们。 - Wybird666

12

我不了解MATLAB中的nargout,也无法想象应该如何正确使用它。然而,您可能希望将重点转向Python函数(或方法)的实际作用。

实际上,Python始终只返回一个值。它要么是None,要么是某种特定类型的单个值,要么是tuple类型的单个对象。

如果没有return命令,则在函数体结束时,函数将返回None。这与在函数体末尾显式写入return(不带参数)或return None相同。

如果使用return None(可以将None存储在变量中)或没有参数的return,则None会返回给调用者。

如果return single,则single对象将返回给调用者。

如果return v1, v2, v3,则实际上返回一个元组(v1、v2、v3)。这只是一种语法糖,您不需要编写括号。

在这种情况下,result1、result2、result3 = f()只是另一种语法糖。 f()返回元组,其元素自动提取到给定的变量中。实际上,您执行以下操作:

result1, result2, result3 = (v1, v2, v3)

或者
t = f()                           # where t is the (v1, v2, v3)
result1, result2, result3 = t

实际上,Python不允许像其他语言一样定义输出参数。你可以想象传递参数对象的地址,如果传递的对象允许修改,则可以进行修改。但是,如果传递的参数最初具有 None 值,则永远无法为传递的参数获得新结果。在Python中没有类似于函数输出参数的机制,也不能通过函数的输出参数来赋值Python变量。

从函数内部返回新创建的值(对象)的唯一自然和直接方法是使用return命令。然而,Python函数并不限制可以返回多少个参数。(好吧,始终只有一个参数,如果它是元组,则稍后可以将其分解为元素。)

如果您想在调用者代码中测试实际返回了什么,您可以编写自己的函数,在以下值返回时执行某些特殊操作:Nonesingle或某个长度的元组(可以使用len(t)获取返回的元组t中的元素数量)。您的函数还可以测试single或每个元组元素的类型,并相应地工作。


1
感谢您的回答。我知道Python的返回类型,以及可以将多个项目作为元组返回。我的问题是关于输出参数的。 - Nik
这段文字在倒数第三个段落中。Python没有输出参数,你只能通过参数传递可修改或不可修改对象。但是每次调用函数都可以传递任何(不同的)对象。参数不能在源代码中标记为可修改或* 输出 *。您必须改变解决方案的思考方式。看到您想要解决的具体示例会很好。 - pepr

5

我通常会让我的函数返回所有东西(作为元组的元素),然后只解压我需要的部分:

def my_func():
    # do stuff...
    return mean, stddev, otherstat, a, b

mu, sig, _, a, _ = myfunc()

这里我返回了所有变量,但只将第1、2和4个变量分配给在调用作用域中使用的变量。_ 变量是一个占位符,用来代替我不想要/不需要的变量。


2
如果更多的人碰巧遇到了这个问题:你还可以获取所需的值并将其余部分解包到一个丢弃的元组中,例如mu, *_ = myfunc()。主要的优点是您不需要知道有多少无用的返回值被丢弃了。 - Guimoute

5
我有一个奇怪的想法…
def nargout():
   import traceback
   callInfo = traceback.extract_stack()
   callLine = str(callInfo[-3].line)
   split_equal = callLine.split('=')
   split_comma = split_equal[0].split(',')
   return len(split_comma)

def fun():
   this = nargout()
   if this == 1:
       return 3
   else:
       return 3, 4

a = fun()
a, b = fun()

我现在真的很想念Matlab :D
改进者:
def nargout(*args):
   import traceback
   callInfo = traceback.extract_stack()
   callLine = str(callInfo[-3].line)
   split_equal = callLine.split('=')
   split_comma = split_equal[0].split(',')
   num = len(split_comma)
   return args[0:num] if num > 1 else args[0]

def fun(nparray):
   return nargout(np.mean(nparray), np.std(nparray))

arr = np.array([3, 4, 5])
mean = fun(arr)
mean, std = fun(arr)

我必须说这并不完美。它会找到调用你的函数的命令行,并通过计算 '=' 前的 ',' 的数量来确定应该返回多少个参数,因此当你的命令被写成多行时,它将无法正常工作... - yiping jiao
不错的想法。 - wander95
我非常喜欢这个想法。在Linux上使用Python 3.9,它可以直接运行。但是,在Windows上使用Python 3.8时,即使分隔符不存在,split命令也会返回一个元素列表 - 我还需要使用traceback.format_list来获取该命令。可以将此想法扩展为多行命令。 - Jens Munk

3
在Matlab中,输出参数的数量实际上是函数的输入之一。但在Python中不是这样的,因此您需要设置函数的接口以反映这种差异。
例如,以下是一个将upper()应用于一组字符串的函数,用户可以期望输入数量等于输出数量。语法也非常类似于Matlab。
>>> def var_returns_f(*args):
...     return [str.upper() for str in args]
... 
>>> 
>>> a, b = var_returns_f('one', 'two')
>>> 
>>> a
'ONE'
>>> b
'TWO'

1

受 @jiao yiping 的启发,我制作了一个装饰器:

from functools import wraps
from traceback import extract_stack

def with_nargout(func):
    @wraps(func)
    def _with_nargout(*args, **kwargs):
        global nargout
        call_line = extract_stack()[-2].line
        if '=' in call_line:
            nargout = len(call_line.split('=')[0].split(','))
        else:
            nargout = 0
        result = func(*args, **kwargs)
        del nargout
        return result
    return _with_nargout

一个例子:

@with_nargout
def func_1(x, y):
    print('nargout is', nargout)
    if nargout == 1:
        return x + y
    elif nargout == 2:
        return x + y, nargout
    print('returning None')

a = func_1(14, 53)
a, b = func_1(15, 43)
print(func_1(14, 39))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接