元组是
Python
中另一个重要的序列结构,和列表类似,也是由一系列按特定顺序排序的元素组成。和列表不同的是,列表可以任意操作元素,是可变序列;而元组是不可变序列,即元组中的元素不可以单独修改。
元组可以看做是不可变的列表。通常情况下,元组用于保存不可修改的内容。
1、从形式上看,元组的所有元素都放在一对小括号“()”中,相邻元素之间用逗号“,”分隔,如下所示:
(element1, element2, ... , elementn)
其中
element1~elementn
表示元组中的各个元素,个数没有限制,且只要是
Python
支持的数据类型就可以。
2、从存储内容上看,元组可以存储整数、实数、字符串、列表、元组等任何类型的数据,并且在同一个元组中,元素的类型可以不同,例如:
("c.biancheng.net",1,[2,'a'],("abc",3.0))
在这个元组中,有多种类型的数据,包括整形、字符串、列表、元组。
创建元组
Python
提供了多种创建元组的方法。
=
运算符直接创建元组
=
在创建元组时,可以使用赋值运算符“=”直接将一个元组赋值给变量,其语法格式如下
tuplename = (element1,element2,...,elementn)
其中,
tuplename
表示创建的元组名,可以使用任何符合
Python
命名规则,且不和
Python
内置函数重名的标识符作为元组名。
下面定义的元组都是合法的:
num = (7,14,21,28,35)
a_tuple = ("C语言中文网","http://c.biancheng.net")
python = ("Python",19,[1,2],('c',2.0))
# 元组通常都是使用一对小括号将所有元素括起来的,
# 但小括号不是必须的,只要将各元素用逗号隔开,Python 就会将其视为元组
a_tuple = "C语言中文网","http://c.biancheng.net"
print(a_tuple)
当创建的元组中只有一个元素时,此元组后面必须要加一个逗号
“,”
,否则
Python
解释器会将其误认为字符串。
#创建元组 a_typle
a_tuple =("C语言中文网",)
print(type(a_tuple))
print(a_tuple)
#创建字符串 a
a = ("C语言中文网")
print(type(a))
print(a)
运行结果为:
<class 'tuple'> # a_tuple 才是元组类型
('C语言中文网',)
<class 'str'> # 变量 a 只是一个字符串
C语言中文网
使用
tuple()
函数创建元组
tuple()
tuple
函数的语法格式如下:
tuple(data)
其中,data 表示可以转化为元组的数据,其类型可以是字符串、元组、range 对象等。
# 将列表转换成元组
a_list = ['crazyit', 20, -1.2]
a_tuple = tuple(a_list)
print(a_tuple)
# 使用range()函数创建区间(range)对象
a_range = range(1, 5)
print(a_range)
# 将区间转换成元组
b_tuple = tuple(a_range)
print(b_tuple)
# 创建区间时还指定步长
c_tuple = tuple(range(4, 20, 3))
print(c_tuple)
运行结果为:
('crazyit', 20, -1.2)
range(1, 5)
(1, 2, 3, 4)
(4, 7, 10, 13, 16, 19)
访问元组元素
想访问元组中的指定元素,可以使用元组中各元素的索引值获取, 也可以采用切片方式获取指定范围内的元素 。
a_tuple = ('crazyit', 20, -1.2)
print(a_tuple[1]) # 20
b_tuple = ('crazyit', 20, -1.2)
#采用切片方式
print(b_tuple[:2]) # ('crazyit', 20)
修改元组元素
元组是不可变序列,元组中的元素不可以单独进行修改。但是,元组也不是完全不能修改。
我们可以对元组进行重新赋值:
a_tuple = ('crazyit', 20, -1.2)
print(a_tuple) # 输出是('crazyit', 20, -1.2)
#对元组进行重新赋值
a_tuple = ('c.biancheng.net',"C语言中文网")
print(a_tuple) # 输出是('c.biancheng.net', 'C语言中文网')
还可以通过连接多个元组的方式向元组中添加新元素。
a_tuple = ('crazyit', 20, -1.2)
print(a_tuple) # 输出是('crazyit', 20, -1.2)
#连接多个元组
a_tuple = a_tuple + ('c.biancheng.net',)
print(a_tuple) # 输出是('crazyit', 20, -1.2, 'c.biancheng.net')
# 元组连接的内容必须都是元组,不能将元组和字符串或列表进行连接,否则或抛出 TypeError 错误。
a_tuple = ('crazyit', 20, -1.2)
#元组连接字符串
a_tuple = a_tuple + 'c.biancheng.net'
print(a_tuple)
# 结果为:
# Traceback (most recent call last):
# File "C:\Users\mengma\Desktop\1.py", line 4, in <module>
# a_tuple = a_tuple + 'c.biancheng.net'
# TypeError: can only concatenate tuple (not "str") to tuple
删除元组
当已经创建的元组确定不再使用时,可以使用
del
语句将其删除
a_tuple = ('crazyit', 20, -1.2)
print(a_tuple)
#删除a_tuple元组
del(a_tuple)
print(a_tuple)
运行结果为:
('crazyit', 20, -1.2)
Traceback (most recent call last):
File "C:\Users\mengma\Desktop\1.py", line 4, in <module>
print(a_tuple)
NameError: name 'a_tuple' is not defined
元组和列表的区别
元组和列表最大的区别就是,列表中的元素可以进行任意修改;而元组中的元素无法修改。
可以理解为,tuple 元组是一个只读版本的 list 列表。
需要注意的是,这样的差异势必会影响两者的存储方式,我们来直接看下面的例子:
>>> listdemo = []
>>> listdemo.__sizeof__()
40
>>> tupleDemo = ()
>>> tupleDemo.__sizeof__()
24
对于列表和元组来说,虽然它们都是空的,但元组却比列表少占用 16 个字节,这是为什么呢?
事实上,就是由于列表是动态的,它需要存储指针来指向对应的元素(占用 8 个字节)。另外,由于列表中元素可变,所以需要额外存储已经分配的长度大小(占用 8 个字节)。但是对于元组,情况就不同了,元组长度大小固定,且存储元素不可变,所以存储空间也是固定的。
通过对比列表和元组存储方式的差异,我们可以引申出这样的结论,即元组要比列表更加轻量级,所以从总体上来说,元组的性能速度要由于列表。
另外,
Python
会在后台,对静态数据做一些资源缓存。通常来说,因为垃圾回收机制的存在,如果一些变量不被使用了,
Python
就会回收它们所占用的内存,返还给操作系统,以便其他变量或其他应用使用。
但是对于一些静态变量(比如元组),如果它不被使用并且占用空间不大时,
Python
会暂时缓存这部分内存。这样的话,当下次再创建同样大小的元组时,
Python
就可以不用再向操作系统发出请求去寻找内存,而是可以直接分配之前缓存的内存空间,这样就能大大加快程序的运行速度。
算初始化一个相同元素的列表和元组分别所需的时间。我们可以看到,元组的初始化速度要比列表快 5 倍。
C:\Users\mengma>python -m timeit 'x=(1,2,3,4,5,6)'
20000000 loops, best of 5: 9.97 nsec per loop
C:\Users\mengma>python -m timeit 'x=[1,2,3,4,5,6]'
5000000 loops, best of 5: 50.1 nsec per loop
列表和元组的底层实现
list
列表具体结构
list
typedef struct {
PyObject_VAR_HEAD
/* Vector of pointers to list elements. list[0] is ob_item[0], etc. */
PyObject **ob_item;
/* ob_item contains space for 'allocated' elements. The number
* currently in use is ob_size.
* Invariants:
* 0 <= ob_size <= allocated
* len(list) == ob_size
* ob_item == NULL implies ob_size == allocated == 0
* list.sort() temporarily sets allocated to -1 to detect mutations.
*
* Items must normally not be NULL, except during construction when
* the list is not yet visible outside the function that builds it.
*/
Py_ssize_t allocated;
} PyListObject;
list
本质上是一个长度可变的连续数组。其中
ob_item
是一个指针列表,里边的每一个指针都指向列表中的元素,而
allocated
则用于存储该列表目前已被分配的空间大小。
需要注意的是,
allocated
和列表的实际空间大小不同,列表实际空间大小,指的是
len(list)
返回的结果,也就是上边代码中注释中的
ob_size
,表示该列表总共存储了多少个元素。而在实际情况中,为了优化存储结构,避免每次增加元素都要重新分配内存,列表预分配的空间
allocated
往往会大于
ob_size
。
因此
allocated
和
ob_size
的关系是:
allocated >= len(list) = ob_size >= 0
。
如果当前列表分配的空间已满(即
allocated == len(list)
),则会向系统请求更大的内存空间,并把原来的元素全部拷贝过去。
元组的具体结构
typedef struct {
PyObject_VAR_HEAD
PyObject *ob_item[1];
/* ob_item contains space for 'ob_size' elements.
* Items must normally not be NULL, except during construction when
* the tuple is not yet visible outside the function that builds it.
*/
} PyTupleObject;
tuple
和
list
相似,本质也是一个数组,但是空间大小固定。不同于一般数组,
Python
的
tuple
做了许多优化,来提升在程序中的效率。
为了提高效率,避免频繁的调用系统函数
free
和
malloc
向操作系统申请和释放空间,
tuple
源文件中定义了一个
free_list
:
static PyTupleObject *free_list[PyTuple_MAXSAVESIZE];
所有申请过的,小于一定大小的元组,在释放的时候会被放进这个
free_list
中以供下次使用。也就是说,如果以后需要再去创建同样的
tuple
,
Python
就可以直接从缓存中载入。