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

选项:使用元组

考虑下面这个简单的例子:

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

但是,随着返回值的增加,这很快就会出现问题。如果你想返回四个或五个值怎么办?当然,您可以继续对它们进行元组,但是很容易忘记哪个值在哪里。在任何你想要收到它们的地方拆开它们也是相当难看的。

选项:使用字典

下一个合乎逻辑的步骤似乎是引入某种“记录符号”。在Python中,最明显的方法是使用字典。

考虑以下几点:

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:
  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__中的项。

Python为小对象提供了一个额外的特性,__slots__属性。类可以表示为:

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__,因此节省了空间。

选项:使用数据类(Python 3.7+)

使用Python 3.7的新数据类,返回一个带有自动添加的特殊方法、类型和其他有用工具的类:

@dataclass
class Returnvalue:
    y0: int
    y1: float
    y3: int

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

选项:使用列表

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

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

这是我最不喜欢的方法。我想我可能因为接触了Haskell而受到了影响,但是混合类型列表的想法总是让我感到不舒服。在这个特殊的例子中,列表是-非-混合类型的,但是可以想象它是混合类型的。

据我所知,以这种方式使用的列表实际上并没有获得任何关于元组的信息。Python中列表和元组之间唯一真正的区别是列表是可变的,而元组不是。

我个人倾向于继承函数式编程的约定:对相同类型的任意数量的元素使用列表,对预定类型的固定数量的元素使用元组。

问题

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


我喜欢:

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

似乎其他一切都只是做同样事情的额外代码。


在Python这样的语言中,我通常会使用字典,因为它比创建一个新类涉及的开销更少。

然而,如果我发现自己总是返回相同的变量集,那么这可能涉及到一个新的类,我将提出因子。


一般来说,“专门化结构”实际上是一个对象的可感知的当前状态,具有自己的方法。

class Some3SpaceThing(object):
  def __init__(self,x):
    self.g(x)
  def g(self,x):
    self.y0 = x + 1
    self.y1 = x * 3
    self.y2 = y0 ** y3

r = Some3SpaceThing( x )
r.y0
r.y1
r.y2

我喜欢尽可能地找到匿名结构的名称。有意义的名字让事情更清楚。


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

返回键值为“y0”、“y1”、“y2”的字典,与元组相比没有任何优势。返回具有.y0、.y1、.y2等属性的ReturnValue实例也没有提供任何优于元组的优势。如果你想去任何地方,你需要开始命名东西,你可以使用元组来做这件事:

def get_image_data(filename):
    [snip]
    return size, (format, version, compression), (width,height)

size, type, dimensions = get_image_data(x)

在我看来,除了元组之外,唯一好的技术是返回具有适当方法和属性的真实对象,就像你从re.match()或open(file)中得到的那样。


当元组感觉“自然”时,我更喜欢使用元组;坐标是一个典型的例子,其中单独的对象可以独立存在,例如在单轴缩放计算中,顺序很重要。注意:如果我可以对项目进行排序或洗牌,而不会对组的含义产生不利影响,那么我可能不应该使用元组。

只有当分组对象不总是相同时,我才使用字典作为返回值。考虑可选的电子邮件标题。

对于其余的情况,分组对象在组内具有固有含义,或者需要具有自己方法的完整对象,我使用类。


我投票给字典。

我发现,如果我让一个函数返回超过2-3个变量,我就会把它们折叠到字典中。否则,我往往会忘记我返回的顺序和内容。

另外,引入一个“特殊”结构会让你的代码更难理解。(其他人将不得不搜索代码来找出它是什么)

如果你关心类型查找,使用描述性字典键,例如,'x-values list'。

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

S.Lott建议的命名容器类+1。

对于Python 2.6及更高版本,命名元组提供了一种轻松创建这些容器类的有用方法,其结果是“轻量级的,不需要比常规元组更多的内存”。


命名元组是在2.6中添加的。参见os。类似的内置示例。

>>> 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+)中,新的类型库增加了NamedTuple类,使命名元组更容易创建,功能更强大。从类型继承。NamedTuple允许您使用文档字符串、默认值和类型注释。

示例(来自文档):

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

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

Python的元组、字典和对象为程序员提供了小型数据结构(“东西”)的正式性和便利性之间的平稳平衡。对我来说,选择如何表示一个东西主要取决于我将如何使用这个结构。在c++中,对于只有数据的项使用struct,对于有方法的对象使用class是一种常见的约定,尽管你可以合法地将方法放在结构上;我的习惯类似于Python,用dict和tuple代替struct。

对于坐标集,我将使用元组而不是点类或字典(请注意,您可以使用元组作为字典键,因此字典可以用于出色的稀疏多维数组)。

如果我要迭代一列东西,我更喜欢在迭代中解包元组:

for score,id,name in scoreAllTheThings():
    if score > goodScoreThreshold:
        print "%6.3f #%6d %s"%(score,id,name)

...由于对象版本读起来更加混乱:

for entry in scoreAllTheThings():
    if entry.score > goodScoreThreshold:
        print "%6.3f #%6d %s"%(entry.score,entry.id,entry.name)

...更不用说字典了。

for entry in scoreAllTheThings():
    if entry['score'] > goodScoreThreshold:
        print "%6.3f #%6d %s"%(entry['score'],entry['id'],entry['name'])

如果对象被广泛使用,并且您发现自己在代码中的多个地方对其进行类似的重要操作,那么通常值得将其设置为具有适当方法的类对象。

最后,如果我要与非python系统组件交换数据,我通常会将它们保存在字典中,因为这最适合JSON序列化。


另一种选择是使用生成器:

>>> def f(x):
        y0 = x + 1
        yield y0
        yield x * 3
        yield y0 ** 4


>>> a, b, c = f(5)
>>> a
6
>>> b
15
>>> c
1296

尽管IMHO元组通常是最好的,除非返回的值是类中封装的候选值。


>>> def func():
...    return [1,2,3]
...
>>> a,b,c = func()
>>> a
1
>>> b
2
>>> c
3

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

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

给:

True
False

我会使用dict来传递和返回函数的值:

使用form中定义的变量形式。

form = {
    'level': 0,
    'points': 0,
    'game': {
        'name': ''
    }
}


def test(form):
    form['game']['name'] = 'My game!'
    form['level'] = 2

    return form

>>> print(test(form))
{u'game': {u'name': u'My game!'}, u'points': 0, u'level': 2}

这对我和处理单位来说都是最有效的方法。

你只需要传入一个指针,返回一个指针。

无论何时在代码中进行更改,您都不必更改函数的参数(成千上万个)。


“最佳”是一个部分主观的决定。在一般情况下,不可变变量是可以接受的,对于小的返回集使用元组。当不需要可变性时,元组总是比列表更可取。

对于更复杂的返回值,或者对于形式很重要的情况(即高值代码),命名元组更好。对于最复杂的情况,对象通常是最好的。然而,真正重要的是情境。如果返回一个对象是有意义的,因为这是你在函数末尾自然拥有的(例如工厂模式),那么返回对象。

正如智者所说:

过早的优化是万恶之源(至少是大部分) 它)在编程。