如何从函数返回多个值?[关闭]


1067

用支持它的语言返回多个值的规范方法通常是麻烦的

选项:使用元组

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

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:
  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 }最终只是__dict__ReturnValue

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中列表和元组之间的唯一真正区别是列表是可变的,而元组则不是。

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

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


10
在您的优秀示例中,您使用了variable y3,但是除非将y3声明为全局变量,否则NameError: global name 'y3' is not defined可能只使用3
hetepeperfan 2013年

11
由于出现了“意见”关键字,许多具有出色答案的伟大问题都被关闭。您可以说整个SO都是基于观点,但是它的观点是由事实,参考和特定的专业知识提供的。仅仅因为有人问“您认为哪个最好”并不意味着他们要从现实中的事实,参考文献和特定专业知识中提炼出个人意见。几乎可以肯定的是,他们确切地要求的是那种观点,这种观点是基于事实,参考文献和该人用来形成观点的特定专业知识的,并以此为依据。
NeilG

@hetepeperfan无需更改3,也不必在全局中定义y3,也可以使用本地名称y3,它也可以完成相同的工作。
okie

Answers:


637

为此,在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 typing.NamedTuple
    name: str
    id: int = 3  # default value

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

68
这是正确的答案,因为这是OP不会考虑的唯一规范结构,并且因为它解决了他管理长元组的问题。应标记为已接受。
空袭

7
嗯,设计的理由namedtuple是为大规模结果(元组的长列表,例如数据库查询的结果)占用较小的内存。对于单个项(如果所讨论的函数不经常调用),字典和类也很好。但是在这种情况下,namedtuples也是一个很好的解决方案。
Lutz Prechelt '02

8
@ wom:不要这样做。Python不会努力统一namedtuple定义(每次调用都会创建一个新的定义),创建namedtuple类在CPU和内存上都相对昂贵,并且所有类定义本质上都涉及循环引用(因此在CPython上,您正在等待循环GC运行)让他们被释放)。这也使pickle类成为不可能(因此,multiprocessing在大多数情况下无法使用实例)。在我的3.6.4 x64上创建该类的每个过程耗时约0.337毫秒,并且占用的内存不足1 KB,从而浪费了所有实例节省的资源。
ShadowRanger

3
我会注意到,Python 3.7 提高了创建新namedtuple类的速度CPU成本下降了约4倍,但仍比创建实例的成本高出约1000倍,并且每个类的内存成本仍然很高(我在关于“不足1 KB”的最后一条评论中错了)对于该类,_source其本身通常为1.5 KB;_source在3.7中已被删除,因此它可能更接近于最初的要求,即每次创建类时不到1 KB)。
ShadowRanger

4
@SergeStroobandt-这是标准库的一部分,而不是内置函数。您不必担心它可能不会安装在Python> = 2.6的另一个系统上。还是您只是反对多余的代码行?
贾斯汀'18

234

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

返回与键的字典"y0""y1""y2"等不提供任何优势元组。返回一个ReturnValue实例与性能.y0.y1.y2等不提供任何元组过任何优势。如果您想到达任何地方,就需要开始命名事物,并且无论如何都可以使用元组来命名:

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

size, type, dimensions = get_image_data(x)

恕我直言,除元组之外,唯一好的技术是使用适当的方法和属性返回真实对象,就像您从re.match()或获取的那样open(file)


6
问题- size, type, dimensions = getImageData(x)和之间有什么区别(size, type, dimensions) = getImageData(x)?即,将元组分配的左侧包装起来有什么区别吗?
Reb.Cabin 2014年

11
@ Reb.Cabin没有区别。元组由逗号标识,括号的使用只是将事物组合在一起。例如(1),一个int while (1,)1,一个元组。
菲尔2015年

19
关于“使用键y0,y1,y2等返回字典没有比元组提供任何优势”:字典的优点在于,您可以在不破坏现有代码的情况下将字段添加到返回的字典中。
ostrokach

关于“使用键y0,y1,y2等返回字典并没有比元组提供任何优势”:当您根据数据的名称而不是位置访问数据时,它更具可读性,并且出错率更低。
Denis Dollfus

204

许多答案表明您需要返回某种类型的集合,例如字典或列表。您可以省去多余的语法,而只需写出返回值(以逗号分隔)即可。注意:从技术上讲,这将返回一个元组。

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

给出:

True
False

24
您仍在返回收藏集。这是一个元组。我更喜欢括号以使其更明确。试试这个:type(f())return <class 'tuple'>
伊戈尔(Igor)

20
@Igor:没有理由使tuple方面明确;返回a并不是很重要tuple,这是返回多个值期间的惯用法。同样的原因,您省略了带有交换习惯用法x, y = y, x,多次初始化x, y = 0, 1等的括号;当然,它使tuples 变得面目全非,但没有理由将其明确化,因为tuples根本不是重点。Python教程在涉及s 之前就引入了多个赋值tuple
ShadowRanger

@ShadowRanger =在Python 右侧有元组的任何一个用逗号分隔的值序列,其周围带有或不带有括号。因此,这里实际上没有显式或隐式。a,b,c是(a,b,c)的元组。当您返回这样的值时,也没有“在幕后”创建元组,因为它只是一个简单的简单元组。OP已经提到过元组,因此他提到的内容与该答案显示的内容实际上没有区别。无
Ken4scholars

2
从字面上看,这是该问题中建议的第一个选项
endolith

1
@endolith两个时间的家伙问一个问题(“我如何返回多个值?”和“如何做返回多个值?”)通过这个答案的回答。问题的文本有时已更改。这是一个基于意见的问题。
Joseph Hansen

74

我投票给字典。

我发现,如果我创建的函数返回的变量超过2-3个,则将它们折叠成字典。否则,我往往会忘记所返回内容的顺序和内容。

另外,引入“特殊”结构会使您的代码更难以遵循。(其他人将不得不搜索代码以找出它是什么)

如果您担心类型查找,请使用描述性字典键,例如“ x值列表”。

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

5
经过多年的编程,我趋向于需要数据和功能的结构。首先运行,您始终可以根据需要进行重构。
monkut 2014年

我们如何在不多次调用函数的情况下获取字典中的值?例如,如果要在其他函数中使用y1和y3?
马特2014年

3
将结果分配给一个单独的变量。 result = g(x); other_function(result)
monkut

1
@monkut是的。这种方式还允许将结果传递给几个函数,这些函数从结果中获取不同的args,而不必每次都专门引用结果的特定部分。
Gnudiff

38

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

>>> 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元组通常是最好的,除非返回的值是封装在类中的候选对象。


1
这似乎是最干净的解决方案,并且语法干净。这有什么缺点吗?如果您不使用所有收益,是否有“未花费”的收益等待伤害您?
Jiminion

24
这可能是“干净的”,但似乎一点也不直观。从未遇到过这种模式的人怎么会知道执行自动元组拆包会触发每个模式yield
coredumperror

1
@CoreDumpError,生成器就是……生成器。def f(x): …; yield b; yield a; yield rvs. 之间没有外部差异(g for g in [b, a, r]),两者都可以轻松转换为列表或元组,因此将支持元组拆包。元组生成器形式遵循一种功能方法,而该功能形式是必不可少的,并且将允许流量控制和变量分配。
sleblanc

30

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

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

对于其余的情况,如果分组的对象在组内具有固有的含义,或者需要具有自己方法的成熟对象,则使用类。


29

我更喜欢:

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

似乎其他所有东西只是做相同事情的额外代码。


22
元组更容易解包:y0,y1,y2 = g(),必须做一个字典:result = g()y0,y1,y2 = result.get('y0'),result.get('y1' ),result.get('y2')有点难看。每个解决方案都有其“优点”和“缺点”。
奥利

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

@edouard不,不是,它返回一个元组而不是列表。
西蒙·希布斯

1
解构是在我看来返回列表参数
semiomant

21

通常,“专用结构”实际上是具有其自身方法的对象的当前状态。

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

我希望在可能的地方找到匿名结构的名称。有意义的名称使事情变得更清楚。


20

Python的元组,字典和对象为程序员提供了在小型数据结构(“事物”)的形式和便利之间的平滑权衡。对我而言,如何表示事物的选择主要取决于我将如何使用结构。在C ++中,即使您可以合法地将方法放在; 上,也struct仅用于纯数据项和class带有方法的对象是一种常见的约定struct。我的习惯与Python类似,用dicttuple代替struct

对于坐标集,我将使用a tuple而不是点class或a dict(并且请注意,您可以将a tuple用作字典键,因此dicts是非常好的稀疏多维数组)。

如果我要遍历所有东西,我更喜欢tuple在迭代中解包s:

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)

...更不用说了dict

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

如果该事物被广泛使用,并且您发现自己在代码中的多个位置对它执行了类似的非平凡操作,那么通常值得用适当的方法将其变成一个类对象。

最后,如果我要与非Python系统组件交换数据,那么我通常会将它们放在a中,dict因为这最适合JSON序列化。


19

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

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


4

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

但是,如果我发现自己不断返回相同的变量集,则可能涉及一个我要考虑的新类。


4

我将使用字典来传递和从函数返回值:

使用form中定义的变量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}

对于我和处理单元而言,这是最有效的方法。

您只需要传递一个指针并返回一个指针即可。

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


字典是可变的。如果将字典传递给函数,并且该函数编辑该字典,则更改将反映在该函数范围之外。让函数在最后返回dict可能意味着该函数没有副作用,因此不应返回该值,这表明test将直接修改该值。与进行比较dict.update,后者不返回值。
sleblanc

@sleblanc“让该函数最后返回dict可能意味着该函数没有副作用”。这并不意味着因为如您所说,字典是可变的。但是,返回form不会损害可读性或性能。如果您可能需要重新格式化form,则返回[form]可以确保form返回了最后一个,因为您不会在任何地方跟踪表单的更改。
Elis Byberi

3

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

对于更复杂的返回值,或者对于形式化很有价值(即高价值代码)的情况,命名元组更好。对于最复杂的情​​况,对象通常是最好的。但是,实际情况才是最重要的。如果返回一个对象是有意义的,因为那是您在函数末尾自然所拥有的(例如Factory模式),则返回该对象。

正如智者所说:

过早的优化是编程中所有邪恶(或至少是大多数邪恶)的根源。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.