协慌网

登录 贡献 社区

如何从函数返回多个值?

在支持它的语言中返回多个值的规范方法通常是tupling

选项:使用元组

考虑这个简单的例子:

def f(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return (y0,y1,y2)

但是,随着返回值的增加,这很快就会出现问题。如果您想要返回四个或五个值,该怎么办?当然,你可以保持它们的组合,但很容易忘记哪个值在哪里。在任何想要接收它们的地方解压缩它们也相当丑陋。

选项:使用字典

下一个合乎逻辑的步骤似乎是引入某种 “记录符号”。在 python 中,显而易见的方法是通过dict

考虑以下:

def g(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return {'y0':y0, 'y1':y1 ,'y2':y2 }

(编辑 - 要清楚,y0,y1 和 y2 只是作为抽象标识符。正如所指出的,在实践中你会使用有意义的标识符)

现在,我们有一个机制,我们可以在其中投射返回对象的特定成员。例如,

result['y0']

选项:使用课程

但是,还有另一种选择。我们可以返回一个专门的结构。我已经在 Python 的上下文中对此进行了构建,但我确信它也适用于其他语言。事实上,如果你在 C 工作,这可能是你唯一的选择。开始:

class ReturnValue(object):
  def __init__(self, y0, y1, y2):
     self.y0 = y0
     self.y1 = y1
     self.y2 = y2

def g(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return ReturnValue(y0, y1, y2)

在 python 中,前两个在管道方面可能非常相似 - 毕竟{ y0, y1, y2 }最终只是ReturnValue内部__dict__中的条目。

虽然对于微小的对象__slots__属性,但 Python 提供了一个附加功能。该课程可表示为:

class ReturnValue(object):
  __slots__ = ["y0", "y1", "y2"]
  def __init__(self, y0, y1, y2):
     self.y0 = y0
     self.y1 = y1
     self.y2 = y2

Python 参考手册

__slots__声明采用一系列实例变量,并在每个实例中保留足够的空间来保存每个变量的值。保存空间是因为没有为每个实例创建__dict__

选项:使用列表

我忽略的另一个建议来自比尔蜥蜴:

def h(x):
  result = [x + 1]
  result.append(x * 3)
  result.append(y0 ** y3)
  return result

这是我最不喜欢的方法。我想我已经被 Haskell 暴露了,但混合型列表的想法一直让我觉得不舒服。在这个特定的例子中,列表是 - not - 混合类型,但可以想象它可以。就我所知,以这种方式使用的列表实际上并没有获得关于元组的任何信息。 Python 中列表和元组之间唯一真正的区别是列表是可变的 ,而元组则不是。我个人倾向于从函数式编程中继承惯例:使用相同类型的任意数量元素的列表,以及预定类型的固定数量元素的元组。

在冗长的序言之后,出现了不可避免的问题。你觉得哪种方法最好?

我通常发现自己去了字典路线,因为它涉及较少的设置工作。但是,从类型的角度来看,你可能最好不要走类路线,因为这可以帮助你避免混淆字典所代表的内容。另一方面,Python 社区中有一些人认为隐含接口应该优先于显式接口 ,此时对象的类型确实不相关,因为您基本上依赖于相同属性的约定将永远具有相同的含义。

那么,你如何在 Python 中返回多个值?

答案

为此目的,在 2.6 中添加了命名元组 。另请参阅os.stat以获取类似的内置示例。

>>> import collections
>>> Point = collections.namedtuple('Point', ['x', 'y'])
>>> p = Point(1, y=2)
>>> p.x, p.y
1 2
>>> p[0], p[1]
1 2

在 Python 3 的最新版本(3.6+,我认为)中,新的typing库得到了NamedTuple类,使命名元组更容易创建,功能更强大。继承自typing.NamedTuple允许您使用文档字符串,默认值和类型注释。

示例(来自文档):

class Employee(NamedTuple):  # inherit from collections.NamedTuple
    name: str
    id: int = 3  # default value

employee = Employee('Guido')
assert employee.id == 3

对于小型项目,我发现使用元组最容易。当它变得难以管理(而不是之前)时,我开始将事物分组到逻辑结构中,但是我认为你建议使用字典和 ReturnValue 对象是错误的(或过于简单化)。

返回带有键 y0,y1,y2 等的字典并不比元组提供任何优势。返回具有属性. y0 .y1 .y2 等的 ReturnValue 实例也不会提供任何优于元组的优势。如果你想要到达任何地方,你需要开始命名,你可以使用元组来做到这一点:

def getImageData(filename):
  [snip]
  return size, (format, version, compression), (width,height)
size, type, dimensions = getImageData(x)

恕我直言,超越元组的唯一好方法是返回具有适当方法和属性的真实对象,就像从re.match()open(file)

很多答案都表明你需要返回某种类型的集合,比如字典或列表。你可以省去额外的语法,然后写出以逗号分隔的返回值。注意:这在技术上会返回一个元组。

def f():
    return True, False
x, y = f()
print(x)
print(y)

得到:

True
False