在支持它的语言中返回多个值的规范方法通常是tupling.
考虑这个简单的例子:
def f(x): y0 = x + 1 y1 = x * 3 y2 = y0 ** y3 return (y0, y1, y2)
但是,随着返回值的增加,这很快就会出现问题.如果您想要返回四个或五个值,该怎么办?当然,你可以保持它们的组合,但很容易忘记哪个值在哪里.在任何想要接收它们的地方解压缩它们也相当丑陋.
下一个合乎逻辑的步骤似乎是引入某种"记录符号".在python中,显而易见的方法是通过a 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__
没有为每个实例创建空间.
我忽略的另一个建议来自比尔蜥蜴:
@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)
这是我最不喜欢的方法.我想我已经被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 typing.NamedTuple name: str id: int = 3 # default value employee = Employee('Guido') assert employee.id == 3
对于小型项目,我发现使用元组最容易.当它变得难以管理(而不是之前)时,我开始将事物分组到逻辑结构中,但是我认为你建议使用字典和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)
恕我直言,超越元组的唯一好方法是使用适当的方法和属性返回真实对象,就像你从ReturnValue
或获得"y0"
.
很多答案都表明你需要返回某种类型的集合,比如字典或列表.你可以省去额外的语法,然后写出以逗号分隔的返回值.注意:这在技术上会返回一个元组.
def f(): return True, False x, y = f() print(x) print(y)
得到:
True False
我投票给字典.
我发现如果我创建一个返回超过2-3个变量的函数,我会将它们折叠起来放在字典中.否则我倾向于忘记我要归还的顺序和内容.
此外,引入"特殊"结构会使您的代码更难以遵循.(其他人必须搜索代码以找出它是什么)
如果您关注类型查找,请使用描述性字典键,例如"x-values list".
def g(x): y0 = x + 1 y1 = x * 3 y2 = y0 ** y3 return {'y0':y0, 'y1':y1 ,'y2':y2 }
另一种选择是使用发电机:
>>> 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
虽然恕我直言的元组通常是最好的,除非返回的值是在类中封装的候选者.
每当元组感觉"自然"时,我更喜欢使用元组; 坐标是一个典型的例子,其中单独的对象可以独立存在,例如,在仅一轴的缩放计算中,顺序很重要.注意:如果我可以对项目进行排序或改组而不会对组的含义产生负面影响,那么我可能不应该使用元组.
仅当分组对象不总是相同时,我才使用字典作为返回值.想想可选的邮箱标题.
对于其他情况,分组对象在组内部具有固有含义,或者需要具有自己方法的完全成熟对象,我使用类.
我更喜欢
def g(x): y0 = x + 1 y1 = x * 3 y2 = y0 ** y3 return {'y0':y0, 'y1':y1 ,'y2':y2 }
似乎其他一切都只是额外的代码来做同样的事情.
>>> def func(): ... return [1,2,3] ... >>> a,b,c = func() >>> a 1 >>> b 2 >>> c 3
通常,"专用结构"实际上是对象的合理当前状态,具有其自己的方法.
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.Lott建议使用命名容器类的+1.
对于python 2.6及更高版本,一个命名元组提供了一种轻松创建这些容器类的有用方法,结果是"轻量级,不需要比常规元组更多的内存".
Python的元组,dicts和对象为程序员提供了在小型数据结构("事物")的形式和便利之间的平滑权衡.对我来说,如何表示事物的选择主要取决于我将如何使用该结构.在C++中,使用struct
仅数据项和class
使用方法的对象是一种常见的约定,即使您可以合法地将方法放在a上struct
; 我的习惯是在Python相似,dict
并且tuple
代替struct
.
对于坐标集,我将使用一个tuple
而不是一个点class
或一个dict
(并注意你可以使用a tuple
作为字典键,因此dict
s可以创建很好的稀疏多维数组).
如果我要迭代一个事物列表,我更喜欢tuple
在迭代上解包:
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系统组件交换数据,我通常会将它们保存在一个中,dict
因为它最适合于JSON序列化.