从Python函数返回多个值的替代方法

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

选项:使用元组

考虑这个简单的例子:

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

然而,随着返回值数量的增加,这很快就会出现问题。如果你想返回四个或五个值怎么办?当然,你可以继续对它们进行元组化,但很容易忘记哪个值在哪里。在任何你想接收它们的地方解包它们也是相当丑陋的。

选项:使用字典

下一个合乎逻辑的步骤似乎是引入某种“记录符号”。在Python中,显而易见的方法是通过dict

考虑以下几点:

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

(需要说明的是,y0、y1和y2只是抽象标识符。如前所述,在实践中您将使用有意义的标识符。)

现在,我们有了一种机制,我们可以通过这种机制投影出返回对象的特定成员。例如,

result['y0']

选项:使用类

然而,还有另一种选择。我们可以返回一个专门的结构。我已经在Python的上下文中构建了这个,但我相信它也适用于其他语言。事实上,如果你用C工作,这很可能是你唯一的选择。下面是:

class ReturnValue:def __init__(self, y0, y1, y2):self.y0 = y0self.y1 = y1self.y2 = y2
def g(x):y0 = x + 1y1 = x * 3y2 = y0 ** y3return 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 = y0self.y1 = y1self.y2 = y2

Python参考手册

__slots__声明接受一系列实例变量,并在每个实例中保留足够的空间来保存每个变量的值。节省空间是因为__dict__不是为每个实例创建的。

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

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

@dataclassclass Returnvalue:y0: inty1: floaty3: int
def total_cost(x):y0 = x + 1y1 = x * 3y2 = y0 ** y3return ReturnValue(y0, y1, y2)

选项:使用列表

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

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

不过,这是我最不喜欢的方法。我想我接触过Haskell,但混合类型列表的想法一直让我感到不舒服。在这个特殊的例子中,列表不是混合类型,但可以想象它可能是。

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

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

问题

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

1362803 次浏览

我更喜欢:

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

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

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

但是,如果我发现自己不断返回相同的变量集,那么这可能涉及到一个新的类,我将把它排除在外。

一般来说,“专用结构”实际上是一个对象的合理当前状态,有自己的方法。

class Some3SpaceThing(object):def __init__(self,x):self.g(x)def g(self,x):self.y0 = x + 1self.y1 = x * 3self.y2 = y0 ** y3
r = Some3SpaceThing( x )r.y0r.y1r.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值列表”。

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

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

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

命名元组是为了这个目的在2.6中添加的。

>>> import collections>>> Point = collections.namedtuple('Point', ['x', 'y'])>>> p = Point(1, y=2)>>> p.x, p.y1 2>>> p[0], p[1]1 2

在最近版本的Python 3(我认为是3.6+)中,新的typing库获得了#1类,以使命名元组更容易创建和更强大。从typing.NamedTuple继承允许您使用文档字符串、默认值和类型注释。

示例(来自文档):

class Employee(NamedTuple):  # inherit from typing.NamedTuplename: strid: int = 3  # default value
employee = Employee('Guido')assert employee.id == 3

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

对于坐标集,我将使用tuple而不是点classdict(请注意,您可以使用tuple作为字典键,因此dict是很棒的稀疏多维数组)。

如果我要迭代一系列东西,我更喜欢在迭代中解压缩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序列化。

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

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

>>> a, b, c = f(5)>>> a6>>> b15>>> c1296

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

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

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

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

提供:

TrueFalse

我会使用字典来传递和返回函数中的值:

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

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}

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

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

每当您在代码中进行更改时,您不必更改函数(数千个)参数。

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

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

正如智者所说:

过早的优化是万恶之源(或者至少是大多数它)在编程中。