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

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

选项:使用元组

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

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

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

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

The canonical way to return multiple values in languages that support it is often tupling.

Option: Using a tuple

Consider this trivial example:

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

However, this quickly gets problematic as the number of values returned increases. What if you want to return four or five values? Sure, you could keep tupling them, but it gets easy to forget which value is where. It’s also rather ugly to unpack them wherever you want to receive them.

Option: Using a dictionary

The next logical step seems to be to introduce some sort of ‘record notation’. In Python, the obvious way to do this is by means of a dict.

Consider the following:

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

(Just to be clear, y0, y1, and y2 are just meant as abstract identifiers. As pointed out, in practice you’d use meaningful identifiers.)

Now, we have a mechanism whereby we can project out a particular member of the returned object. For example,

result['y0']

Option: Using a class

However, there is another option. We could instead return a specialized structure. I’ve framed this in the context of Python, but I’m sure it applies to other languages as well. Indeed, if you were working in C this might very well be your only option. Here goes:

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)

In Python the previous two are perhaps very similar in terms of plumbing – after all { y0, y1, y2 } just end up being entries in the internal __dict__ of the ReturnValue.

There is one additional feature provided by Python though for tiny objects, the __slots__ attribute. The class could be expressed as:

class ReturnValue(object):
  __slots__ = ["y0", "y1", "y2"]
  def __init__(self, y0, y1, y2):
     self.y0 = y0
     self.y1 = y1
     self.y2 = y2

From the Python Reference Manual:

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.

Option: Using a dataclass (Python 3.7+)

Using Python 3.7’s new dataclasses, return a class with automatically added special methods, typing and other useful tools:

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

Option: Using a list

Another suggestion which I’d overlooked comes from Bill the Lizard:

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

This is my least favorite method though. I suppose I’m tainted by exposure to Haskell, but the idea of mixed-type lists has always felt uncomfortable to me. In this particular example the list is -not- mixed type, but it conceivably could be.

A list used in this way really doesn’t gain anything with respect to the tuple as far as I can tell. The only real difference between lists and tuples in Python is that lists are mutable, whereas tuples are not.

I personally tend to carry over the conventions from functional programming: use lists for any number of elements of the same type, and tuples for a fixed number of elements of predetermined types.

Question

After the lengthy preamble, comes the inevitable question. Which method (do you think) is best?


回答 0

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

Named tuples were added in 2.6 for this purpose. Also see os.stat for a similar builtin example.

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

In recent versions of Python 3 (3.6+, I think), the new typing library got the NamedTuple class to make named tuples easier to create and more powerful. Inheriting from typing.NamedTuple lets you use docstrings, default values, and type annotations.

Example (From the docs):

class Employee(NamedTuple):  # inherit from typing.NamedTuple
    name: str
    id: int = 3  # default value

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

回答 1

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

For small projects I find it easiest to work with tuples. When that gets too hard to manage (and not before) I start grouping things into logical structures, however I think your suggested use of dictionaries and ReturnValue objects is wrong (or too simplistic).

Returning a dictionary with keys "y0", "y1", "y2", etc. doesn’t offer any advantage over tuples. Returning a ReturnValue instance with properties .y0, .y1, .y2, etc. doesn’t offer any advantage over tuples either. You need to start naming things if you want to get anywhere, and you can do that using tuples anyway:

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

size, type, dimensions = get_image_data(x)

IMHO, the only good technique beyond tuples is to return real objects with proper methods and properties, like you get from re.match() or open(file).


回答 2

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

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

给出:

True
False

A lot of the answers suggest you need to return a collection of some sort, like a dictionary or a list. You could leave off the extra syntax and just write out the return values, comma-separated. Note: this technically returns a tuple.

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

gives:

True
False

回答 3

我投票给字典。

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

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

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

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

I vote for the dictionary.

I find that if I make a function that returns anything more than 2-3 variables I’ll fold them up in a dictionary. Otherwise I tend to forget the order and content of what I’m returning.

Also, introducing a ‘special’ structure makes your code more difficult to follow. (Someone else will have to search through the code to find out what it is)

If your concerned about type look up, use descriptive dictionary keys, for example, ‘x-values list’.

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

回答 4

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

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

Another option would be using generators:

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

Although IMHO tuples are usually best, except in cases where the values being returned are candidates for encapsulation in a class.


回答 5

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

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

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

I prefer to use tuples whenever a tuple feels “natural”; coordinates are a typical example, where the separate objects can stand on their own, e.g. in one-axis only scaling calculations, and the order is important. Note: if I can sort or shuffle the items without an adverse effect to the meaning of the group, then I probably shouldn’t use a tuple.

I use dictionaries as a return value only when the grouped objects aren’t always the same. Think optional email headers.

For the rest of the cases, where the grouped objects have inherent meaning inside the group or a fully-fledged object with its own methods is needed, I use a class.


回答 6

我更喜欢:

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

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

I prefer:

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

It seems everything else is just extra code to do the same thing.


回答 7

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

回答 8

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

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

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

Generally, the “specialized structure” actually IS a sensible current state of an object, with its own methods.

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

I like to find names for anonymous structures where possible. Meaningful names make things more clear.


回答 9

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序列化。

Python’s tuples, dicts, and objects offer the programmer a smooth tradeoff between formality and convenience for small data structures (“things”). For me, the choice of how to represent a thing is dictated mainly by how I’m going to use the structure. In C++, it’s a common convention to use struct for data-only items and class for objects with methods, even though you can legally put methods on a struct; my habit is similar in Python, with dict and tuple in place of struct.

For coordinate sets, I’ll use a tuple rather than a point class or a dict (and note that you can use a tuple as a dictionary key, so dicts make great sparse multidimensional arrays).

If I’m going to be iterating over a list of things, I prefer unpacking tuples on the iteration:

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

…as the object version is more cluttered to read:

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

…let alone the dict.

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

If the thing is widely used, and you find yourself doing similar non-trivial operations on it in multiple places in the code, then it’s usually worthwhile to make it a class object with appropriate methods.

Finally, if I’m going to be exchanging data with non-Python system components, I’ll most often keep them in a dict because that’s best suited to JSON serialization.


回答 10

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

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

+1 on S.Lott’s suggestion of a named container class.

For Python 2.6 and up, a named tuple provides a useful way of easily creating these container classes, and the results are “lightweight and require no more memory than regular tuples”.


回答 11

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

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

In languages like Python, I would usually use a dictionary as it involves less overhead than creating a new class.

However, if I find myself constantly returning the same set of variables, then that probably involves a new class that I’ll factor out.


回答 12

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

使用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}

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

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

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

I would use a dict to pass and return values from a function:

Use variable form as defined in 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}

This is the most efficient way for me and for processing unit.

You have to pass just one pointer in and return just one pointer out.

You do not have to change functions’ (thousands of them) arguments whenever you make a change in your code.


回答 13

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

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

正如智者所说:

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

“Best” is a partially subjective decision. Use tuples for small return sets in the general case where an immutable is acceptable. A tuple is always preferable to a list when mutability is not a requirement.

For more complex return values, or for the case where formality is valuable (i.e. high value code) a named tuple is better. For the most complex case an object is usually best. However, it’s really the situation that matters. If it makes sense to return an object because that is what you naturally have at the end of the function (e.g. Factory pattern) then return the object.

As the wise man said:

Premature optimization is the root of all evil (or at least most of it) in programming.


声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。