nargout in Python

2019-01-15 12:15发布

问题:

Does Python have any equivalent of nargout in MATLAB? I find nargout a very neat approach if we want to keep the number of return parameters flexible. Is there a way I can find out how many output parameters have been requested? Something like the following pseudo-python-code:

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

So if I call this function as mean = var_returns_func(someNumPyarray), it should return a single value. But if I call it as mean,std = var_returns_func(someNumPyarray), it should return 2 values.

Is there a Pythonic way of doing this? Or a hacky way?

回答1:

The function can't know what is going to be done with the return value, so it can't know how many are needed. What you could do is pass nargout as an argument to your function and use that to decide what to return:

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

This is an example of "explicit is better than implicit" as a Python philosophy. If you want two arguments out, you have to say explicitly that you want two arguments out. Implicitly deciding based on looking into the future to see what is going to be done with the result is discouraged in Python. It's perfectly possible to do a = f(x) and want to get a two-element tuple in a.

For examples like yours, there are lots of better ways. One is to just do mean, std = a.mean(), a.std(), or more generally x, y = someFunc(a), otherFunc(a). If this particular combination of values is commonly needed, and there is some expensive computation shared by both operations that you don't want to duplicate, make a third function that clearly returns both and do x, y = funcThatReturnsTwoThings(a). All of these are explicit ways of keeping functions separate if they do different things.



回答2:

I cannot speak for nargout in MATLAB as I do not know it and I cannot imagine how it should be used correctly. However, you may want to change you view to what a Python function (or method) really does).

Actually, Python always returns exactly one value. Or it is None, or it is a single value of some specific type, or it is a single object of the tuple type.

If there is no return command, the function returns None if the function body ends. It is the same as if you explicitly wrote return without arguments at the end of body, or return None at the end of body.

If you use return None (the None can be stored in a variable) or return without arguments, the None is returne to the caller.

If you return single, the single object is return to the caller.

If you return v1, v2, v3, you actually return a tuple (v1, v2, v3). It is just a syntactic sugar that you need not to write the parentheses.

The result1, result2, result3 = f() in the case is just another syntactic sugar. The f() returns the tuple and its elements are automatically extracted to the given variables. You actually do:

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

or

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

Actually, Python does not allow to define output arguments as it is usual in other languages. You can think in terms you are passing addresses of the argument objects and if the passed object allows to be modified, you can modify it. But you can never get a new result to a passed argument that had initially None value, for example. You cannot assign a Python variable via output argument of the function -- no mechanism like that in Python.

The only natural and direct way to return newly created values (objects) from inside the function is to use the return command. However, a Python function does not limits you how many arguments can be returned. (Well, always a single one that can be later broken to elements if it was a tuple.)

If you want to test in the caller code what was actually returned, you can write your own function that does something special in cases where the following values were returned: None, single, a tuple of some length (len(t) can be used to get the number of elements in the returned tuple t). Your function can also test the type of single or of the each of the tuple elements and work accordingly.



回答3:

In Matlab, the number of output arguments is actually one of the inputs to a function. This is not the case in Python and so you have set up the function's interface to reflect this differently.

For example, here's a function that applies upper() to a group of strings, and the user can expect the the number of inputs equals the number of outputs. The syntax is also very similar to Matlab's.

>>> def var_returns_f(*args):
...     return [str.upper() for str in args]
... 
>>> 
>>> a, b = var_returns_f('one', 'two')
>>> 
>>> a
'ONE'
>>> b
'TWO'


回答4:

What I tend to do is make my function return everything (as elements of a tuple), then only unpack the ones I need:

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

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

Here Im returning everything, but only assigning the 1st 2nd and 4th variables to variables I will use in the calling scope. The _ variable is a throwaway to act as a placeholder for the variables I dont want/need.



回答5:

I have a strange idea ...

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

I really miss Matlab now :D


Improved by:

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)