用支持它的语言返回多个值的规范方法通常是tupling。
选项:使用元组考虑这个简单的例子:
1 2 3 4 5 | def f(x): y0 = x + 1 y1 = x * 3 y2 = y0 ** y3 return (y0,y1,y2) |
但是,随着返回值数量的增加,这很快就会出现问题。如果想返回4或5个值呢?当然,您可以继续对它们进行tupling,但是很容易忘记哪个值在哪里。在任何你想接收它们的地方打开它们也是相当丑陋的。
选项:使用字典下一个合乎逻辑的步骤似乎是引入某种"记录符号"。在python中,最明显的方法是使用
考虑以下:
1 2 3 4 5 | def g(x): y0 = x + 1 y1 = x * 3 y2 = y0 ** y3 return {'y0':y0, 'y1':y1 ,'y2':y2 } |
(编辑-要清楚,y0、y1和y2只是作为抽象标识符。正如所指出的,在实践中,您将使用有意义的标识符)
现在,我们有了一种机制,通过这种机制,我们可以投射出返回对象的特定成员。例如,
1 | result['y0'] |
选项:使用类
然而,还有另一种选择。我们可以返回一个专门的结构。我在Python上下文中构建了这个框架,但我确信它也适用于其他语言。事实上,如果你在C语言领域工作,这可能是你唯一的选择。是:
1 2 3 4 5 6 7 8 9 10 11 | 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中,前两种方法在管道方面可能非常相似——毕竟
不过Python还为小对象提供了一个额外的特性,
1 2 3 4 5 6 | class ReturnValue(object): __slots__ = ["y0","y1","y2"] def __init__(self, y0, y1, y2): self.y0 = y0 self.y1 = y1 self.y2 = y2 |
从Python参考手册:
The
__slots__ declaration takes a sequence of instance variables and reserves just enough space in each instance to hold a value for each variable. Space is saved because__dict__ is not created for each instance.
选项:使用列表
另一个我忽略的建议来自蜥蜴比尔:
1 2 3 4 5 | def h(x): result = [x + 1] result.append(x * 3) result.append(y0 ** y3) return result |
这是我最不喜欢的方法。我想我受到了Haskell的影响,但是混合类型列表的想法总是让我感到不舒服。在这个特殊的例子中,列表是-not- mixed类型,但是可以想象它是混合类型。就我所知,以这种方式使用的列表对元组没有任何好处。Python中列表和元组之间唯一真正的区别是列表是可变的,而元组不是。我个人倾向于继承函数式编程中的约定:对任意数量的相同类型的元素使用列表,对固定数量的预定义类型的元素使用元组。
问题
在冗长的序言之后,出现了一个不可避免的问题。你认为哪种方法最好?
我通常会选择字典的方式,因为它需要较少的设置工作。然而,从类型的角度来看,您最好选择类路径,因为这可以帮助您避免混淆dictionary所表示的内容。另一方面,Python社区中有一些人认为隐式接口应该优于显式接口,在这种情况下,对象的类型实际上是无关的,因为您基本上依赖的约定是相同的属性总是具有相同的含义。
那么,如何在Python中返回多个值呢?
为此,在2.6中添加了命名元组。也看到操作系统。stat用于类似的构建示例。
1 2 3 4 5 6 7 | >>> 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+),新的
示例(来自文档):
1 2 3 4 5 6 | class Employee(NamedTuple): # inherit from collections.NamedTuple name: str id: int = 3 # default value employee = Employee('Guido') assert employee.id == 3 |
对于小型项目,我发现使用元组最容易。当这变得难以管理时(在此之前也是如此),我开始将事物分组到逻辑结构中,但是我认为您建议使用dictionary和ReturnValue对象是错误的(或者过于简单)。
与元组相比,返回带有y0、y1、y2等键的字典没有任何优势。返回具有.y0 .y1 .y2等属性的ReturnValue实例也没有提供任何优于元组的优势。你需要开始命名的东西,如果你想去任何地方,你可以用元组无论如何:
1 2 3 4 | def getImageData(filename): [snip] return size, (format, version, compression), (width,height) size, type, dimensions = getImageData(x) |
依我之见,除了元组之外,惟一好的技术是使用适当的方法和属性返回实际对象,就像您从
很多答案都建议您需要返回某种类型的集合,比如字典或列表。您可以省略额外的语法,只写出以逗号分隔的返回值。注意:这在技术上返回一个元组。
1 2 3 4 5 | def f(): return True, False x, y = f() print(x) print(y) |
给:
1 2 | True False |
我投票赞成这本词典。
我发现,如果我创建一个函数,返回任何超过2-3个变量,我将把它们折叠到字典中。否则,我往往会忘记返回内容的顺序和内容。
此外,引入一个"特殊"结构会使代码更难理解。(其他人将不得不搜索代码来找出它是什么)
如果您关心类型查找,请使用描述性字典键,例如"x-values list"。
1 2 3 4 5 | def g(x): y0 = x + 1 y1 = x * 3 y2 = y0 ** y3 return {'y0':y0, 'y1':y1 ,'y2':y2 } |
另一个选择是使用生成器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | >>> 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 2 3 4 5 6 7 8 9 10 | >>> def func(): ... return [1,2,3] ... >>> a,b,c = func() >>> a 1 >>> b 2 >>> c 3 |
我更喜欢
1 2 3 4 5 | def g(x): y0 = x + 1 y1 = x * 3 y2 = y0 ** y3 return {'y0':y0, 'y1':y1 ,'y2':y2 } |
似乎所有其他的东西都只是额外的代码来做同样的事情。
Python的元组、dict和对象为程序员提供了小数据结构("事物")的形式性和方便性之间的平衡。对我来说,如何表示一个东西的选择主要取决于我将如何使用这个结构。在c++中,对于只有数据的项使用
对于坐标集,我将使用
如果我要迭代一列东西,我更喜欢在迭代中解包
1 2 3 | for score,id,name in scoreAllTheThings(): if score > goodScoreThreshold: print"%6.3f #%6d %s"%(score,id,name) |
…因为对象版本读起来更混乱:
1 2 3 | for entry in scoreAllTheThings(): if entry.score > goodScoreThreshold: print"%6.3f #%6d %s"%(entry.score,entry.id,entry.name) |
…更不用说亚南了。
1 2 3 | for entry in scoreAllTheThings(): if entry['score'] > goodScoreThreshold: print"%6.3f #%6d %s"%(entry['score'],entry['id'],entry['name']) |
如果这个对象被广泛使用,并且您发现自己在代码的多个位置对它进行了类似的非平凡操作,那么通常有必要使用适当的方法使它成为一个类对象。
最后,如果我要与非python系统组件交换数据,我通常将它们保存在
一般来说,"专门化结构"实际上是对象的可感知的当前状态,具有自己的方法。
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
我喜欢尽可能地为匿名结构寻找名称。有意义的名字会让事情更清楚。
在S + 1。Lott对命名容器类的建议。
对于python 2.6及以上版本,命名元组提供了一种方便地创建这些容器类的有用方法,结果是"轻量级的,不需要比常规元组更多的内存"。
在像Python这样的语言中,我通常会使用字典,因为它涉及的开销比创建一个新类要少。
然而,如果我发现自己总是返回相同的变量集,那么这可能涉及到一个我将提出的新类。
我将使用dict来传递和返回函数的值:
使用表单中定义的变量表单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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} |
这对我和处理单元来说是最有效的方法。
只需要传入一个指针,然后返回一个指针。
当您在代码中进行更改时,您不必更改函数(成千上万个函数)的参数。
"最佳"是一个部分主观的决定。在接受不可变的一般情况下,对小的返回集使用元组。当不需要可变性时,元组总是比列表更可取。
对于更复杂的返回值,或者对于形式很重要的情况(即高值代码),命名元组更好。对于最复杂的情况,对象通常是最好的。然而,真正重要的是情境。如果返回一个对象是有意义的,因为这是您在函数末尾自然拥有的东西(例如Factory pattern),那么返回这个对象。
正如智者所说:
Premature optimization is the root of all evil (or at least most of
it) in programming.