1-Python - tuple
About
学到这里应该会想到列表和字符串有很多的共同属性,像索引和切片,它们都是序列数据类型的两个基本组成,这里再来学习一种序列数据类型——元组(tuple)。
元组的基本操作
创建元组
Python中,元组(tuple)用一对小括号()
表示,元组内的各元素以逗号分隔。
t = ()
print(type(t)) # <type 'tuple'>
t1 = ('name', )
print(t1) # ('name',)
print(type(t1)) # <type 'tuple'>
元组中特别注意逗号的使用,一不小心创建的元组就会成为字符串。
t = ('name')
print(t, type(t)) # ('name', <type 'str'>)
t1 = ('name', )
print(t1, type(t1)) # (('name',), <type 'tuple'>)
索引和切片
元组中索引和切片的用法跟列表和字符串类似,或者取范围内的值,或者可以指定在一段范围内,每几个取一个:
t = ('a', 'b', 'c', 'd', 'e')
# 按照索引取值
print(t[1]) # b
print(t[-1]) # e
# 取两者之间(范围)的值
print(t[0:4]) # ('a', 'b', 'c', 'd')
print(t[0:5:2]) # ('a', 'c', 'e')
# 反转元组,返回反转后的新的元组,原本的元组不变
print(t[::-1]) # ('e', 'd', 'c', 'b', 'a')
# for循环取值
for i in t:
print(i)
'''
a
b
c
d
e
'''
但与列表不同的是,元组身为不可变类型,无法原地修改其中的值。
t = ('a', 'b', 'c', 'd', 'e')
t[2] = 'w'
'''
TypeError: 'tuple' object does not support item assignment
'''
**拼接: + **
虽然元组无法原地修改其中的元素,但是,通过+
号可以把两个元组合并为一个新的元组。
t1 = ('a', 'b')
t2 = ('c', 'd', 'e')
t3 = t1 + t2
print(t3) # ('a', 'b', 'c', 'd', 'e')
**元组的重复: ***
简单来说,正如字符串的重复一样,当对元组使用*
时,复制指定次数后合并为一个新的元组。
t1 = ('a', 'b')
t2 = t1 * 2
print(t2) # ('a', 'b', 'a', 'b')
成员资格测试: in,not in
与字符串、列表的成员资格测试在元组中同样适用:
t1 = ('a', 'b', 'abc')
print('a' in t1) # True
print('b' not in t1) # False
需要注意的是,成员资格判断,只是会判断某个元素是否存是元组的一级元素,比如上例中的a
是元组的一级元素。而c
不是元组的一级元素。在元组中,c
是元组一级元素字符串abc
的子串。所以判断结果为False。
序列(元组)类型的打包与解包
t = 1, 2, 3
x, y, z = t
print(x, y, z) # 1 2 3
上例第1行,将1、2、3打包赋值给变量t,相当于将3个苹果打包到一个盒子内。第2行,从盒子t中将3个苹果取出来,分别交给x、y、z,我们称为解包。解包这里需要注意的是,盒子里有几个苹果,必须有几个对应的变量接收。多了不行,少了也不行。
平行赋值
>>> x, y = 1, 2
>>> x,y
(1, 2)
>>> x
1
>>> type(x)
<class 'int'>
>>> a = x,y
>>> a
(1, 2)
>>> type(a)
<class 'tuple'>
如上例第1行所示,平行赋值就是等号右边的1,2分别赋值给等号左边的x,y。第2行就是打包了(只是打包,并没有赋值给某个变量),并且打包后的结果是元组类型。而在第8行将x,y打包并赋值给变量a。此时a就是打包后的元组了。
通过打印斐波那契序列来练习平行赋值:
x, y = 0, 1
while x < 8:
x, y = y, x + y
print(x)
'''
1
1
2
3
5
8
'''
首先定义x,y两个变量并赋值。在每次循环中,x和y值都会重新赋值。
删除元组
注意,这里说的删除仅是删除整个元组,而不是删除元组中某个元素。
>>> t = (1, 2, 3)
>>> del t[1]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object doesn't support item deletion
>>> del t
>>> t
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 't' is not defined
上例第2行,通过删除元组内的元素导致报错,又一次的证明元组为不可变类型。但我们可以删除整个元组(第6行)。
来总结,元组中常用的操作符:
操作符(表达式) | 描述 | 重要程度 |
---|---|---|
+ | 合并 | ** |
* | 重复 | ** |
in | 成员资格 | **** |
for i in (1, 2, 3):print(i) | 迭代 | ***** |
t[2] | 索引取值 | ***** |
t[start:stop:step] | 切片(截取) | ***** |
另外,还有几个内置的函数可以应用于元组:
方法 | 描述 | 重要程度 |
---|---|---|
max(tuple) | 返回元组内最大的元素 | ** |
min(tuple) | 返回元组内最小的元素 | ** |
tuple(seq) | 将序列转换为元组 | ***** |
len(tuple) | 返回元组长度 | ***** |
元组的嵌套
与列表一样,元组也能嵌套存储数据:
t = (1, (2, 3), [4, [5, 'c']], {'a': 'b'}, {8, 7})
for item in t:
print(item)
'''
1
(2, 3)
[4, [5, 'c']]
{'a': 'b'}
{8, 7}
'''
元组内可以存储的数据类型相当丰富,也可以发现,嵌套元素也会当成一个整体称为元组的子元素。但是我们说元组是不可更改的,但我们发现其中是由列表的,这是怎么回事?
t = (1, (2, 3), [4, [5, 'c']], {'a': 'b'}, {8, 7})
t[0] = 'x' # TypeError: 'tuple' object does not support item assignment
通过上例,可以发现,元组内的普通元素不允许修改,但是列表是可变,我们能否把列表替换为别的元素呢?
t = (1, (2, 3), [4, [5, 'c']], {'a': 'b'}, {8, 7})
print(t[2]) # [4, [5, 'c']]
t[2] = 'w' # TypeError: 'tuple' object does not support item assignment
通过上例也可以发现,如果列表被当成了元组的普通元素,那么它也是不可以修改的,遵循元组不可变特性。但是我们如果试图修改列表中的子元素呢?
t = (1, (2, 3), [4, [5, 'c']], {'a': 'b'}, {8, 7})
t[2][0] = 'w'
print(t) # (1, (2, 3), ['w', [5, 'c']], {'a': 'b'}, {8, 7})
上例,可以发现,t[2][0]
指的是列表中的第一个元素,我们在第2行修改它,然后发现是可以修改成功的。这是为什么呢?元组内的普通元素不允许修改,嵌套的子元素是否能够修改取决于这个子元素本身属于什么数据类型,如这个子元素是列表,那就可以修改,如果是元组,就可不以修改。
那么,了解完元组,你可能更有疑问了,除了不可变之外,元组跟列表没啥区别么?我们通过与列表的对比,来证明存在即是真理!
需要注意的是,元组并没有增、删功能。那么元组如此设计,以放弃增、删为代价换来了什么呢?
性能!是的,换来了性能!不信请看,以下演示代码由IPython(Python的另一个发行版本)完成。
list VS tuple
list VS tuple:创建(生成)速度
我们通过创建同样大小的list和tuple,来观察有什么变化:
In [1]: % timeit [1, 2, 3, 4, 5]
139 ns ± 2.34 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [2]: % timeit (1, 2, 3, 4, 5)
17.3 ns ± 0.161 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)
上例的意思是说我们创建一个长度为5的列表,大概执行了7次大循环,每次大循环中进行10000000
次小循环。这个7次大循环,每次耗时(平均值)139 ns
,每次(标准)偏差2.34 ns
。而创建同样长度的元组,每次仅耗时17.3 ns
,对比创建列表的耗时139 ns
,可以看到创建元组的速度快很多。
list VS tuple:遍历速度
In [13]: from numpy.random import rand
In [14]: values = rand(5, 2)
In [15]: values
Out[15]:
array([[0.58715281, 0.80168228],
[0.18092562, 0.38003109],
[0.7041874 , 0.36891089],
[0.49066082, 0.4369031 ],
[0.66990039, 0.61642406]])
In [16]: l = [list(row) for row in values]
In [17]: l
Out[17]:
[[0.5871528101592326, 0.801682278879223],
[0.1809256206032368, 0.3800310899685857],
[0.704187400986516, 0.36891089280681766],
[0.4906608153801344, 0.43690309811990113],
[0.6699003858521562, 0.6164240631243966]]
In [18]: t = tuple(tuple(row) for row in values)
In [19]: t
Out[19]:
((0.5871528101592326, 0.801682278879223),
(0.1809256206032368, 0.3800310899685857),
(0.704187400986516, 0.36891089280681766),
(0.4906608153801344, 0.43690309811990113),
(0.6699003858521562, 0.6164240631243966))
In [20]: % timeit for row in l: list(row)
4.73 µs ± 78.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [21]: % timeit for row in t: tuple(row)
823 ns ± 26.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
我们循环遍历元组和列表,可以看到遍历列表耗时4.73 µs
,而元组这边耗时823 ns
。
要知道1微秒(μs)=1000纳秒(ns)
,元组的遍历性能相比列表也是快很多了。
扩展:
1秒 = 1000毫秒
1毫秒 = 1000微秒
1微秒 = 1000纳秒
1纳秒 = 1000皮秒
1s = 1000ms
1ms = 1000μs
1μs = 1000ns
1ns = 1000ps
list VS tuple:存储开销
再来看他们的存储开销:
from sys import getsizeof
l = [1, 2, 3, 4, 5]
t = (1, 2, 3, 4, 5)
print(getsizeof(l)) # 56
print(getsizeof(t)) # 48
可以看到,元组在存储空间上面也占优势。
list VS tuple:哈希比较
l = [1, 2, 3, 4, 5]
t = (1, 2, 3, 4, 5)
print(hash(t)) # -1883319094
print(hash(l)) # TypeError: unhashable type: 'list'
简单来说,Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
Python中可哈希(hashable)类型:字符串、元组、对象。可哈希类型就是我们常说的不可变类型,优点是性能经过优化,多线程安全,不需要锁,不担心被恶意篡改或者不小心修改了。
不可哈希类型:字典、列表、集合。相对于可哈希类型,使用起来相对灵活。
欢迎斧正,that's all,see also: