问题:如何从函数返回多个值?[关闭]
用支持它的语言返回多个值的规范方法通常是麻烦的。
选项:使用元组
考虑下面这个简单的例子:
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
该
__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中列表和元组之间的唯一真正区别是列表是可变的,而元组则不是。
我个人倾向于继承函数式编程的约定:对任何数量的相同类型的元素使用列表,对固定数量的预定类型的元素使用元组。
题
在冗长的序言之后,出现了不可避免的问题。(您认为)哪种方法最好?
回答 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
回答 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)
。
回答 2
许多答案表明您需要返回某种类型的集合,例如字典或列表。您可以省去多余的语法,而只需写出返回值(以逗号分隔)即可。注意:从技术上讲,这将返回一个元组。
def f():
return True, False
x, y = f()
print(x)
print(y)
给出:
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 }
回答 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元组通常是最好的,除非返回的值是封装在类中的候选对象。
回答 5
我更喜欢在元组感到“自然”时使用元组。坐标是一个典型示例,其中单独的对象可以独立站立,例如在单轴缩放计算中,顺序很重要。注意:如果我可以对项目进行排序或改组而不会对组的含义造成不利影响,那么我可能不应该使用元组。
仅当分组的对象并不总是相同时,我才使用字典作为返回值。考虑可选的电子邮件标题。
对于其余的情况,如果分组的对象在组内具有固有的含义,或者需要具有自己方法的成熟对象,则使用类。
回答 6
我更喜欢:
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return {'y0':y0, 'y1':y1 ,'y2':y2 }
似乎其他所有东西只是做相同事情的额外代码。
回答 7
>>> 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
我希望在可能的地方找到匿名结构的名称。有意义的名称使事情变得更清楚。
回答 9
Python的元组,字典和对象为程序员提供了在小型数据结构(“事物”)的形式和便利之间的平滑权衡。对我而言,如何表示事物的选择主要取决于我将如何使用结构。在C ++中,即使您可以合法地将方法放在; 上,也struct
仅用于纯数据项和class
带有方法的对象是一种常见的约定struct
。我的习惯与Python类似,用dict
和tuple
代替struct
。
对于坐标集,我将使用a tuple
而不是点class
或a dict
(并且请注意,您可以将a tuple
用作字典键,因此dict
s是非常好的稀疏多维数组)。
如果我要遍历所有东西,我更喜欢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序列化。
回答 10
S.Lott关于命名容器类的建议的+1。
对于Python 2.6及更高版本,命名元组提供了一种轻松创建这些容器类的有用方法,其结果是“重量轻,并且不需要比常规元组更多的内存”。
回答 11
在像Python这样的语言中,我通常会使用字典,因为与创建新类相比,它所涉及的开销更少。
但是,如果我发现自己不断返回相同的变量集,则可能涉及一个我要考虑的新类。
回答 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}
对于我和处理单元而言,这是最有效的方法。
您只需要传递一个指针并返回一个指针即可。
在代码中进行更改时,不必更改函数的参数(成千上万个)。
回答 13
“最佳”是部分主观的决定。在可接受不可变的一般情况下,将元组用于小的收益集。当不需要可变性时,元组总是比列表更可取。
对于更复杂的返回值,或者对于形式化很有价值(即高价值代码)的情况,命名元组更好。对于最复杂的情况,对象通常是最好的。但是,实际情况才是最重要的。如果返回一个对象是有意义的,因为那是您在函数末尾自然所拥有的(例如Factory模式),则返回该对象。
正如智者所说:
过早的优化是编程中所有邪恶(或至少是大多数邪恶)的根源。