为什么我们需要Python中的元组(或任何不可变数据类型)?
我看过几个关于Python的教程(比如《Dive Into Python》),还有Python.org上的语言参考文档,但我不明白为什么这个语言需要元组(tuple)。
元组跟列表(list)或集合(set)相比,没有什么方法可用。如果我必须把元组转换成集合或列表才能排序,那使用元组的意义何在呢?
不可变性?
为什么有人会在意一个变量在内存中的位置和它最开始分配时是否不同?在Python中,这种不可变性似乎被过分强调了。
在C/C++中,如果我分配了一个指针并指向某个有效的内存地址,我并不在乎这个地址在哪里,只要在使用之前它不是空的就行。
每次我引用那个变量时,我不需要知道这个指针是否还指向原来的地址。我只需要检查它是否为空,然后使用它(或者不使用)。
在Python中,当我分配一个字符串(或元组)并把它赋值给x,然后修改这个字符串时,我为什么要在意它是否是原来的对象?只要这个变量指向我的数据,那就够了。
>>> x='hello'
>>> id(x)
1234567
>>> x='good bye'
>>> id(x)
5432167
x
仍然指向我想要的数据,为什么有人需要在意它的ID是否相同或不同呢?
9 个回答
如果我必须把元组转换成集合或列表才能排序,那我一开始用元组有什么意义呢?
在这种情况下,可能确实没有什么意义。这个问题其实不算问题,因为这并不是你会考虑使用元组的情况。
正如你所说,元组是不可变的。不可变类型存在的原因同样适用于元组:
- 复制效率:与其复制一个不可变对象,不如直接引用它(把变量绑定到一个引用上)
- 比较效率:当你使用引用复制时,可以通过比较位置来比较两个变量,而不是比较内容
- 内部存储:你最多只需要存储一个不可变值的副本
- 在并发代码中,不需要同步对不可变对象的访问
- 常量正确性:有些值不应该被改变。这(对我来说)是不可变类型的主要原因。
需要注意的是,某个特定的Python实现可能不会利用上述所有特性。
字典的键必须是不可变的,否则改变键对象的属性可能会破坏底层数据结构的稳定性。因此,元组可以作为键使用。这是常量正确性的一个结果。
上面提到的答案没有指出元组和列表之间的真正区别,这一点很多刚接触Python的人似乎还不太明白。
元组和列表的用途不同。列表用来存储同类的数据。你可以并且应该有这样的列表:
["Bob", "Joe", "John", "Sam"]
之所以这样使用列表是因为这些都是同一种类型的数据,具体来说,就是人名。但是如果你有这样的列表:
["Billy", "Bob", "Joe", 42]
这个列表包含了一个人的全名和他们的年龄。这不是同一种类型的数据。正确的存储方式应该是用元组,或者用一个对象。假设我们有几个:
[("Billy", "Bob", "Joe", 42), ("Robert", "", "Smith", 31)]
元组和列表的不可变性和可变性并不是主要区别。列表是同一种物品的集合:文件、名字、对象。而元组则是不同类型对象的组合。它们有不同的用途,很多Python程序员错误地用列表来做元组应该做的事情。
请不要这样做。
补充:
我觉得这篇博客文章比我说得更清楚,解释了我为什么这么认为:
不可变对象可以让程序运行得更快,这可能就是为什么Java中的字符串也是不可变的。Java和Python是差不多同时开发的,真正的函数式编程语言几乎所有东西都是不可变的。
在Python中,只有不可变的对象才能被哈希(也就是说,它们可以作为集合的成员或者字典的键)。这样做不仅能提高效率,还能解决很多麻烦。如果你要设计一个哈希表来存储可变对象,那简直是一场噩梦——要么你在哈希的时候就复制所有东西,要么就得担心对象的哈希值在你最后一次引用它之后有没有改变。
优化问题的例子:
$ python -mtimeit '["fee", "fie", "fo", "fum"]'
1000000 loops, best of 3: 0.432 usec per loop
$ python -mtimeit '("fee", "fie", "fo", "fum")'
10000000 loops, best of 3: 0.0563 usec per loop