从 Python 函数返回多个值的替代方法[关闭]
- 2024-11-27 10:43:00
- admin 原创
- 12
问题描述:
在支持它的语言中,返回多个值的规范方法通常是元组。
选项:使用元组
考虑这个简单的例子:
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 中列表和元组之间的唯一真正区别是列表是可变的,而元组不是。
我个人倾向于延续函数式编程的惯例:使用列表来表示任意数量的相同类型的元素,使用元组来表示固定数量的预定类型的元素。
问题
冗长的序言过后,不可避免的问题来了。您认为哪种方法最好?
解决方案 1:
为此,在 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
解决方案 2:
对于小型项目,我发现使用元组最容易。当元组变得难以管理时(以前不会),我开始将事物分组为逻辑结构,但我认为您建议的字典和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)
。
解决方案 3:
很多答案都建议你需要返回某种类型的集合,比如字典或列表。你可以省去额外的语法,直接写出返回值,以逗号分隔。注意:这在技术上返回一个元组。
def f():
return True, False
x, y = f()
print(x)
print(y)
给出:
True
False
解决方案 4:
我投票支持这本字典。
我发现,如果我创建一个返回超过 2-3 个变量的函数,我会将它们折叠到字典中。否则我很容易忘记返回内容的顺序和内容。
此外,引入“特殊”结构会使您的代码更难理解。(其他人必须搜索代码才能找出它是什么)
如果您担心类型查找,请使用描述性字典键,例如“x 值列表”。
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return {'y0':y0, 'y1':y1 ,'y2':y2 }
解决方案 5:
另一种选择是使用生成器:
>>> 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
尽管在我看来元组通常是最好的,但返回的值适合封装在类中的情况除外。
解决方案 6:
我更喜欢:
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return {'y0':y0, 'y1':y1 ,'y2':y2 }
看起来其他一切都只是完成同样事情的额外代码。
解决方案 7:
只要元组感觉“自然”,我更喜欢使用元组;坐标就是一个典型的例子,其中单独的对象可以独立存在,例如在单轴缩放计算中,顺序很重要。注意:如果我可以对项目进行排序或打乱顺序而不会对组的含义产生不利影响,那么我可能不应该使用元组。
仅当分组对象并不总是相同时,我才使用字典作为返回值。想想可选的电子邮件标题。
对于其余的情况,当分组对象在组内具有固有含义或者需要具有自身方法的成熟对象时,我使用类。
解决方案 8:
>>> def func():
... return [1,2,3]
...
>>> a,b,c = func()
>>> a
1
>>> b
2
>>> c
3
解决方案 9:
一般来说,“专门的结构”实际上是对象的合理当前状态,具有其自己的方法。
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:
Python 的元组、字典和对象为程序员提供了一种在小型数据结构(“事物”)的正式性和便利性之间的平衡。对我来说,如何表示事物的选择主要取决于我将如何使用该结构。在 C++ 中,struct
对于纯数据项和class
具有方法的对象,使用 是一种常见的惯例,尽管你可以合法地将方法放在 上struct
;我在 Python 中的习惯是类似的,用dict
和tuple
代替struct
。
对于坐标集,我将使用tuple
而不是点class
或dict
(请注意,您可以使用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 序列化。
解决方案 11:
对 S.Lott 关于命名容器类的建议 +1。
对于 Python 2.6 及更高版本,命名元组提供了一种轻松创建这些容器类的有用方法,其结果是“轻量级并且不需要比常规元组更多的内存”。
解决方案 12:
“最佳”是一个部分主观的决定。在可接受不可变的一般情况下,对小返回集使用元组。当可变性不是必需时,元组总是比列表更可取。
对于更复杂的返回值,或者形式化很重要的情况(即高价值代码),命名元组更好。对于最复杂的情况,对象通常是最好的。然而,情况才是最重要的。如果返回一个对象是有意义的,因为这是函数末尾自然拥有的东西(例如工厂模式),那么返回该对象。
正如那位智者所说:
过早优化是编程中一切罪恶的根源(或者至少是大部分罪恶的根源)。
解决方案 13:
在 Python 等语言中,我通常会使用字典,因为它比创建新类所需的开销更少。
然而,如果我发现自己不断地返回同一组变量,那么这可能涉及到一个我将分解出来的新类。
解决方案 14:
我将使用字典来从函数传递和返回值:
使用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}
对于我和处理单位来说,这是最有效的方式。
您只需传入一个指针并返回一个指针即可。
当您的代码发生更改时,您不必更改函数(数千个)的参数。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理必备:盘点2024年13款好用的项目管理软件