Python:可变长度元组
[Python 3.1]
我在关注一个设计理念,就是元组(tuple)应该是长度已知的(可以参考这个评论),而长度未知的元组在大多数情况下应该用列表(list)来替代。我的问题是,在什么情况下我可以不遵循这个规则呢?
举个例子,我知道从字符串和数字字面量创建元组比创建列表要快(可以参考另一个评论)。所以,如果我有一些对性能要求很高的代码,比如有很多计算像是sumproduct(tuple1, tuple2)
,我应该把它们重新定义为使用列表,尽管这样会影响性能吗?(sumproduct((x, y, z), (a, b, c))
的定义是x * a + y * b + z * c
,而它的参数长度是未知但相等的)。
还有,当我使用def f(*x)
时,Python会自动生成一个元组。我想这不是我每次使用都需要转换成列表的东西吧。
顺便问一下,(x, y, z)
比[x, y, z]
创建得更快吗?(这里是指变量而不是字面量)
4 个回答
在什么情况下我应该偏离“元组应该是已知长度”的规则呢?
没有。
这其实是个关于意义的问题。如果一个对象的意义是基于固定数量的元素,那么它就是一个元组。比如 (x,y) 坐标、(c,m,y,k) 颜色、(lat, lon) 位置等等。
元组的元素数量是根据问题的领域和具体情况来决定的。
设计一个元素数量不确定的元组是没有什么意义的。我们什么时候会从 (x,y) 变成 (x,y,z),再到 (x,y,z,w) 的坐标呢?这可不是简单地把一个值加到列表里那么简单。如果我们是从二维坐标转到三维坐标,通常需要一些复杂的数学来映射这些坐标系统,而不是简单地往列表里添加一个元素。
从 (r,g,b) 颜色变成其他颜色意味着什么呢?在 rgb 系统中,第四种颜色是什么?那么在 cmyk 系统中,第五种颜色又是什么呢?
元组的大小是不会改变的。
*args
是一个元组,因为它是不可变的。没错,它可以有不确定数量的参数,但这只是一个少见的例外,和已知、固定大小的元组相比。
那么对于一个长度不确定的元组该怎么办呢?这个例外情况非常重要,以至于我们有两个选择。
拒绝元组是固定长度的这个想法,认为它受到问题的限制。因为这个例外情况,(x,y) 坐标和 (r,g,b) 颜色的概念完全没有价值,都是错误的。固定长度的元组?绝对不行。
总是把所有的
*args
转换成列表,以便始终遵循设计原则。转换成列表?永远如此。
我喜欢这种非黑即白的选择,因为它让软件工程变得简单而不需要思考。
也许在这些特殊情况下,确实需要一点“思考”的成分。只是一点点。
没错,*args
是一个元组。是的,它的长度不确定。没错,它是一个例外情况,在这里“由问题领域决定的固定性”被“简单的不可变性”所超越。
这让我们在某些情况下有了第三个选择:如果一个序列因为其他原因是不可变的,你永远不会去改变它,那么它可以是一个不定大小的元组。在更少见的情况下,如果你因为把 *args
当作栈或队列来使用而需要弹出值,那么你可能想把它变成一个列表。但我们无法提前解决所有可能的问题。
有时候,确实需要思考。
在设计时,你设计一个元组是有原因的。是为了给你的数据施加一个有意义的结构。元素数量固定?用元组。元素数量可变(也就是可变的)?用列表。
我总是选择最合适的数据结构来完成任务,并不太在意使用元组能否让我节省半毫秒的时间。提前让代码变得复杂通常不会带来好处。如果代码运行得太慢,你可以稍后进行性能分析,找出那0.01%真正影响性能的代码进行修改。
你提到的所有问题都和你使用的Python版本以及运行它的硬件有关。你可以自己测试一下,看看在你的机器上这些操作的时间是多少。
一个常见的例子就是“旧版不可变字符串拼接很慢”这个说法。在大约十年前,这个说法是对的,但在2.4或2.5版本中,他们改变了实现方式。如果你自己测试一下,现在字符串拼接的速度比列表还快,但很多人仍然相信这个说法,使用一些实际上运行得更慢的奇怪写法!
在我看来,元组和列表之间唯一有趣的区别就是:列表是可以改变的,而元组则不能。其他人提到的区别对我来说完全是人为的:元组像结构体,列表像数组(这就是“元组应该是固定长度”的来源)。但是,结构体的特性和不可变性有什么关系呢?其实没有关系。
唯一重要的区别就是编程语言所定义的:可变性。如果你需要修改这个对象,肯定要用列表。如果你需要把这个对象作为字典的键,或者作为集合的元素,那么你需要它是不可变的,所以就用元组。就这么简单。