当前位置:  开发笔记 > 编程语言 > 正文

如何从函数返回多个值?

如何解决《如何从函数返回多个值?》经验,为你挑选了11个好方法。

在支持它的语言中返回多个值的规范方法通常是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中返回多个值?



1> A. Coady..:

为此目的,在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


这只是正确答案,因为它是OP没有考虑的唯一规范结构,因为它解决了管理长元组的问题.应标记为已接受.
那么,[``namedtuple``](https://docs.python.org/3/library/collections.html#collections.namedtuple)的设计原理是_mass_结果的内存占用量较小(长元组列表) ,例如DB查询的结果).对于单个项目(如果不经常调用有问题的函数),字典和类也可以.但是在这种情况下,namedtuples也是一个不错/更好的解决方案.
@wom:不要这样做.Python没有努力统一`namedtuple`定义(每个调用创建一个新的定义),创建`namedtuple`类在CPU和内存中相对昂贵,并且所有类定义本质上都涉及循环引用(所以在CPython上,你是等待循环GC运行以释放它们).它也使得不可能"挑选"类(因此,在大多数情况下,不可能使用`multiprocessing`实例).我的3.6.4 x64上的每个类的创建消耗~0.337毫秒,并且占用不到1 KB的内存,从而消除了任何实例节省.
@SergeStroobandt - 这是标准库的一部分,不是内置的.您不必担心它可能不会安装在Python> = 2.6的另一个系统上.或者你只是反对额外的代码行?
我会注意到,Python 3.7 [提高了创建新`namedtuple`类的速度](https://docs.python.org/3.7/whatsnew/3.7.html#api-and-feature-removals);[CPU成本下降了大约4倍](https://bugs.python.org/issue28638#msg298631),但它们仍比创建实例的成本和每个实例的内存成本高约1000倍。类仍然很高(我在关于该类的“低于1 KB”的最后一条评论中错了,`_source`本身通常为1.5 KB;`_source`在3.7中被删除,因此它可能更接近于原来的声明,称“低于1 KB”每次创建班级1 KB)。

2> too much php..:

对于小型项目,我发现使用元组最容易.当它变得难以管理(而不是之前)时,我开始将事物分组到逻辑结构中,但是我认为你建议使用字典和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".


重新"返回带有键y0,y1,y2等的字典并不提供任何优于元组的优势":字典的优势在于您可以在不破坏现有代码的情况下向返回的字典添加字段.
@ Reb.Cabin没有区别.元组由逗号标识,括号的使用只是将事物组合在一起.例如`(1)`是一个int,而`(1,)`或`1,`是一个元组.
问题 - `size,type,dimensions = getImageData(x)`和`(size,type,dimensions)= getImageData(x)`之间有什么区别吗?即,包装左侧的包装是否有任何区别?

3> Joseph Hanse..:

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

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

得到:

True
False


你还在回收一个系列.这是一个元组.我更喜欢括号使其更明确.试试这个:``type(f())``返回````.
@Igor:没有理由让`tuple`方面明确; 你返回一个`tuple`并不是很重要,这是返回多个值期间的习语.你用交换习语,`x,y = y,x`,多个初始化`x,y = 0,1`等等省略了parens的原因相同; 当然,它使`tuple在引擎盖下,但没有理由说明这一点,因为`tuple不是重点.Python教程[引入多项任务](https://docs.python.org/3/tutorial/introduction.html#first-steps-towards-programming)早在它甚至触及`tuple之前.
这实际上是问题中建议的第一个选项

4> monkut..:

我投票给字典.

我发现如果我创建一个返回超过2-3个变量的函数,我会将它们折叠起来放在字典中.否则我倾向于忘记我要归还的顺序和内容.

此外,引入"特殊"结构会使您的代码更难以遵循.(其他人必须搜索代码以找出它是什么)

如果您关注类型查找,请使用描述性字典键,例如"x-values list".

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


经过多年的编程,我倾向于需要数据和功能的结构.功能首先,您可以随时重构.
将结果分配给单独的变量.`result = g(x); other_function(结果)`

5> rlms..:

另一种选择是使用发电机:

>>> 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

虽然恕我直言的元组通常是最好的,除非返回的值是在类中封装的候选者.


这可能是"干净的",但它看起来根本不直观.从来没有遇到过这种模式的人怎么会知道做自动元组解包会触发每个`yield`?

6> tzot..:

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

仅当分组对象不总是相同时,我才使用字典作为返回值.想想可选的邮箱标题.

对于其他情况,分组对象在组内部具有固有含义,或者需要具有自己方法的完全成熟对象,我使用类.



7> UnkwnTech..:

我更喜欢

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

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


元组更容易解压缩:y0,y1,y2 = g(),你需要做一个dict:result = g()y0,y1,y2 = result.get('y0'),result.get('y1' ),result.get('y2')这有点难看.每种解决方案都有其"优点"和"缺点".

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



9> S.Lott..:

通常,"专用结构"实际上是对象的合理当前状态,具有其自己的方法.

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

我喜欢在可能的情况下找到匿名结构的名称.有意义的名字使事情变得更加清晰.



10> John Fouhy..:

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

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



11> Russell Boro..:

Python的元组,dicts和对象为程序员提供了在小型数据结构("事物")的形式和便利之间的平滑权衡.对我来说,如何表示事物的选择主要取决于我将如何使用该结构.在C++中,使用struct仅数据项和class使用方法的对象是一种常见的约定,即使您可以合法地将方法放在a上struct; 我的习惯是在Python相似,dict并且tuple代替struct.

对于坐标集,我将使用一个tuple而不是一个点class或一个dict(并注意你可以使用a tuple作为字典键,因此dicts可以创建很好的稀疏多维数组).

如果我要迭代一个事物列表,我更喜欢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序列化.

推荐阅读
喜生-Da
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有