## 元组简介
列表非常适合用于存储在程序运行期间可能变化的数据集。列表是可以修改的,这对处理网站的用户列表或游戏中的角色列表至关重要。然而,有时候你需要创建一系列不可修改的元素,元组可以满足这种需求。 Python将不能修改的值称为不可变的,而不可变的列表被称为元组。
## 定义元组
元组看起来犹如列表,但使用圆括号而不是方括号来标识。定义元组后,就可以使用索引来访问其元素,就像访问列表元素一样。
例如,如果有一个大小不应改变的矩形,可将其长度和宽度存储在一个元组中,从而确保它们是不能修改的:
```
dimensions = (200, 50)
print(dimensions[0])
print(dimensions[1])
200
50
```
我们首先定义了元组dimensions,为此我们使用了圆括号而不是方括号。接下来,我们分别打印该元组的各个元素,使用的语法与访问列表元素时使用的语法相同:
下面来尝试修改元组dimensions中的一个元素,看看结果如何:
```
dimensions = (200, 50)
dimensions[0] = 250
```
第二行处的代码试图修改第一个元素的值,导致Python返回类型错误消息。由于试图修改元组的操作是被禁止的,因此Python指出不能给元组的元素赋值:
```
Traceback (most recent call last):
File "dimensions.py", line 3, in
dimensions[0] = 250
TypeError: 'tuple' object does not support item assignment
```
代码试图修改矩形的尺寸时, Python报告错误,这很好,因为这正是我们希望的。
## 遍历元组中的所有值
像列表一样,也可以使用for循环来遍历元组中的所有值:
```
dimensions = (200, 50)
for dimension in dimensions:
print(dimension)
200
50
```
就像遍历列表时一样, Python返回元组中所有的元素。
## 修改元组变量
虽然不能修改元组的元素,但可以给存储元组的变量赋值。因此,如果要修改前述矩形的尺寸,可重新定义整个元组:
```
dimensions = (200, 50)
print("Original dimensions:")
for dimension in dimensions:
print(dimension)
dimensions = (400, 100)
print("\nModified dimensions:")
for dimension in dimensions:
print(dimension)
```
我们首先定义了一个元组,并将其存储的尺寸打印了出来;接下来,将一个新元组存储到变量dimensions中;然后,打印新的尺寸。这次, Python不会报告任何错误,因为给元组变量赋值是合法的:
```
Original dimensions:
200
50
Modified dimensions:
400
100
```
相比于列表,元组是更简单的数据结构。如果需要存储的一组值在程序的整个生命周期内都不变,可使用元组。
## 字符串格式化
在许多编程语言中都包含有格式化字符串的功能,比如C和Fortran语言中的格式化输入输出。Python中内置有对字符串进行格式化的操作%。
**模板**
格式化字符串时,Python使用一个字符串作为模板。模板中有格式符,这些格式符为真实值预留位置,并说明真实数值应该呈现的格式。Python用一个tuple将多个值传递给模板,每个值对应一个格式符。
比如下面的例子:
```
print("I'm %s. I'm %d year old" % ('Vamei', 99))
```
上面的例子中,
"I'm %s. I'm %d year old" 为我们的模板。%s为第一个格式符,表示一个字符串。%d为第二个格式符,表示一个整数。('Vamei', 99)的两个元素'Vamei'和99为替换%s和%d的真实值。
在模板和tuple之间,有一个%号分隔,它代表了格式化操作。
整个"I'm %s. I'm %d year old" % ('Vamei', 99) 实际上构成一个字符串表达式。我们可以像一个正常的字符串那样,将它赋值给某个变量。比如:
```
a = "I'm %s. I'm %d year old" % ('Vamei', 99)
print(a)
```
我们还可以用词典来传递真实值。如下:
```
print("I'm %(name)s. I'm %(age)d year old" % {'name':'Vamei', 'age':99})
```
可以看到,我们对两个格式符进行了命名。命名使用()括起来。每个命名对应词典的一个key。
**格式符**
格式符为真实值预留位置,并控制显示的格式。格式符可以包含有一个类型码,用以控制显示的类型,如下:
* %s 字符串 (采用str()的显示)
* %r 字符串 (采用repr()的显示)
* %c 单个字符
* %b 二进制整数
* %d 十进制整数
* %i 十进制整数
* %o 八进制整数
* %x 十六进制整数
* %e 指数 (基底写为e)
* %E 指数 (基底写为E)
* %f 浮点数
* %F 浮点数,与上相同
* %g 指数(e)或浮点数 (根据显示长度)
* %G 指数(E)或浮点数 (根据显示长度)
* %% 字符"%"
可以用如下的方式,对格式进行进一步的控制:
```
%[(name)][flags][width].[precision]typecode
```
(name)为命名
flags可以有+,-,' '或0。+表示右对齐。-表示左对齐。' '为一个空格,表示在正数的左侧填充一个空格,从而与负数对齐。0表示使用0填充。
width表示显示宽度
precision表示小数点后精度
比如:
```
print("%+10x" % 10)
print("%04d" % 5)
print("%6.3f" % 2.3)
```
上面的width, precision为两个整数。我们可以利用\*,来动态代入这两个量。比如:
print("%.*f" % (4, 1.2))*
*Python实际上用4来替换*。所以实际的模板为"%.4f"。
Python中内置的%操作符可用于格式化字符串操作,控制字符串的呈现格式。Python中还有其他的格式化字符串的方式,但%操作符的使用是最方便的。
### 使用元组
Python 的元组与列表类似,不同之处在于元组的元素不能修改,在前面的代码中我们已经不止一次使用过元组了。顾名思义,我们把多个元素组合到一起就形成了一个元组,所以它和列表一样可以保存多条数据。下面的代码演示了如何定义和使用元组。
```Python
def main():
# 定义元组
t = ('骆昊', 38, True, '四川成都')
print(t)
# 获取元组中的元素
print(t[0])
print(t[3])
# 遍历元组中的值
for member in t:
print(member)
# 重新给元组赋值
# t[0] = '王大锤' # TypeError
# 变量t重新引用了新的元组原来的元组将被垃圾回收
t = ('王大锤', 20, True, '云南昆明')
print(t)
# 将元组转换成列表
person = list(t)
print(person)
# 列表是可以修改它的元素的
person[0] = '李小龙'
person[1] = 25
print(person)
# 将列表转换成元组
fruits_list = ['apple', 'banana', 'orange']
fruits_tuple = tuple(fruits_list)
print(fruits_tuple)
if __name__ == '__main__':
main()
```
这里有一个非常值得探讨的问题,我们已经有了列表这种数据结构,为什么还需要元组这样的类型呢?
1. 元组中的元素是无法修改的,事实上我们在项目中尤其是[多线程](https://zh.wikipedia.org/zh-hans/%E5%A4%9A%E7%BA%BF%E7%A8%8B)环境(后面会讲到)中可能更喜欢使用的是那些不变对象(一方面因为对象状态不能修改,所以可以避免由此引起的不必要的程序错误,简单的说就是一个不变的对象要比可变的对象更加容易维护;另一方面因为没有任何一个线程能够修改不变对象的内部状态,一个不变对象自动就是线程安全的,这样就可以省掉处理同步化的开销。一个不变对象可以方便的被共享访问)。所以结论就是:如果不需要对元素进行添加、删除、修改的时候,可以考虑使用元组,当然如果一个方法要返回多个值,使用元组也是不错的选择。
2. 元组在创建时间和占用的空间上面都优于列表。我们可以使用sys模块的getsizeof函数来检查存储同样的元素的元组和列表各自占用了多少内存空间,这个很容易做到。我们也可以在ipython中使用魔法指令%timeit来分析创建同样内容的元组和列表所花费的时间,下图是我的macOS系统上测试的结果。
![](./res/ipython-timeit.png)