Python 数据结构之 List and Tuple

关于 Python 基本数据类型,我们已经介绍了数字(Number)、字符串(String)。这一小节我们来看 Python 中的 元组(tuple)和列表(list)

元组和列表都属于序列(Sequence),在开始介绍元组(tuple)和列表(list)之前我们需要先简单了解一下序列。

Python 中的序列

序列是一系列数据元素的集合,是 Python 中最基本的一种数据结构。

序列中的每个元素都分配一个 数字(指元素的位置,或索引),第一个索引是 0,第二个索引是 1,依此类推。每个索引对应一个元素

Python 包含 6 中内建的序列,包括列表元组字符串Unicode 字符串buffer 对象xrange 对象

对于序列,都可以使用以下操作:

  1. 索引
  2. 切片
  3. 成员检查
  4. 计算序列的长度
  5. 取序列中的最大、最小值

你可以和我们前面介绍过的字符串(String)类型进行一一验证(都介绍过~~~)。

关于序列详细解读可以参见博文:Python 中的序列详解


列表(List)

List(列表) 是 Python 中使用最频繁的数据类型。

列表的创建

和前面我们介绍过的数值(Number )和字符串(str)类似,我们如何创建一个列表?提供三种方法:

[1] >>>> 列表的标准创建格式:

只要把 逗号分隔的不同的数据元素(有序) 使用 方括号(“[]”)括起来就可以完成一个列表的定义,如下:

1
2
3
>>> classmates = ['Michael', 'Bob', 'Tracy']
>>> classmates
['Michael', 'Bob', 'Tracy']

但请注意,列表中的数据元素可以具有不同的数据类型(支持数字、字符串,甚至可以包含列表(所谓的列表嵌套),以及后续将要学习的元组、字典、集合等):

1
2
3
4
5
6
	>>> list1 = ['string test', 123.321, [1,2], {"name":1,"age":2}, (1,2,"test")]
>>> list1
['string test', 123.321, [1, 2], {'name': 1, 'age': 2}, (1, 2, 'test')]

>>> type(list1)
<class 'list'>

注意:在实际使用列表时,虽然可以将不同类型的数据放入到同一个列表中,但通常情况下不这么做,同一列表中只放入同一类型的数据,这样可以提高程序的可读性。

空列表 >>>>

使用此方式创建列表时,列表中元素可以有多个,也可以一个都没有(空列表),例如:

1
2
3
>>> emptylist = []
>>> emptylist
[]

[2] >>>> list(Iterable) 内建函数创建列表:

除了使用 [] 创建列表外,Python 还提供了一个内置的函数 list(Iterable),使用它可以将其它可迭代对象转换为列表类型。

关于 Python 中可迭代对象的说明请参见博文:Python 中的可迭代对象(Iterable)

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 将字符串转换成列表:
list1 = list("hello")
print(list1)

# 将元组转换成列表:
tuple1 = ('Python', 'Java', 'C++', 'JavaScript')
list2 = list(tuple1)
print(list2)

# 将字典转换成列表
dict1 = {'a':100, 'b':42, 'c':9}
list3 = list(dict1)
print(list3)

# 将区间转换成列表:
range1 = range(1, 6)
list4 = list(range1)
print(list4)

# 创建空列表
list5 = list()
print(list5)

运行结果如下

1
2
3
4
5
['h', 'e', 'l', 'l', 'o']
['Python', 'Java', 'C++', 'JavaScript']
['a', 'b', 'c']
[1, 2, 3, 4, 5]
[]

[3] >>>> 推导式方法创建列表:

除此之外,我们还可以使用 推导式方法 创建列表:

1
2
3
>>> list2 = [x for x in "abcdefg"]
>>> list2
['a', 'b', 'c', 'd', 'e', 'f', 'g']

========================================================

实际情况下,第 2,3 中方法使用较多,尤其是通过 range() 函数可以快速初始化一个数字列表。

range() 函数常常和 Python 循环结构、推导式(后续会讲,这里先不涉及)一起使用,几乎能够创建任何需要的数字列表。


列表的删除

对于已经创建的列表,如果不再使用,可以使用 del 关键字将其删除。

实际开发中并不经常使用 del 来删除列表,因为 Python 自带的垃圾回收机制会自动销毁无用的列表,即使开发者不手动删除,Python 也会自动将其回收。

del 关键字的语法格式为:

1
del listname

Python 删除列表实例演示:

1
2
3
4
5
6
7
8
9
10
>>> intlist = [1, 45, 8, 34]
>>> print(intlist)
[1, 45, 8, 34]

>>> del intlist
>>> print(intlist)
Traceback (most recent call last):
File "C:\Users\xxxxx\Desktop\demo.py", line 4, in <module>
print(intlist)
NameError: name 'intlist' is not defined

序列支持的操作

由于列表(List)属于 Python 的内建序列,故序列(seq)中包含的基本操作 List 也具有。例如:1.索引、2.切片、3.加、4.乘、5.成员检查、6.获取序列的长度、7.取序列中的最大、最小值。

列表索引和切片

[1] >>>> 访问单个列表元素

列表中,可以用索引来访问 list 中每一个对应位置的元素。索引是从 0 开始的(-1 为从末尾的开始位置的逆向索引):

1
2
3
4
5
6
7
8
9
10
11
>>> classmates = ['Michael', 'Bob', 'Tracy']
>>> len(classmates)
3
>>> classmates[0]
'Michael'
>>> classmates[1]
'Bob'
>>> classmates[-1]
'Tracy'
>>> classmates[-2]
'Bob'

对于嵌套列表元素的特殊索引:

1
2
3
4
5
6
7
8
9
>>> list1 = ['str1', 'str2', ['list_element1', 'list_element2']]
>>> list1[0]
'str1'
>>> list1[2]
['list_element1', 'list_element2']
>>> list1[2][0]
'list_element1'
>>> list1[2][1]
'list_element2'

注意,当索引超出了 list 范围时,Python 会报一个 IndexError 错误,所以,要确保索引不要越界(超出列表的容量)。

1
2
3
4
>>> classmates[3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range

[2] >>>> 切片

与字符串的分割(切片)一样,我们可以通过[]: 对列表进行切片操作,以获得列表中的多个元素对象:

1
2
3
4
5
6
7
>>> list1 = [5,6,7,8,9]
>>> print(list1[:])
[5,6,7,8,9]
>>> print(list2[2:])
[7,8,9]
>>> print(list2[2:4])
[7,8]

和字符串一样,我们甚至可以指定切片的步长(不常用):

1
2
3
4
5
6
>>> classmates = ['Michael', 'Bob', 'Tracy']
>>> classmates[0:len(classmates):2]
['Michael', 'Tracy']
>>> str = "weqrqewrewq"
>>> str[0:len(str):2]
'wqqweq'

列表拼接(+)和重复(*)

1
2
3
4
5
6
>>> list1 = [1.0, 'welcome']
>>> list2 = ["python", "world"]
>>> print(list1 * 3)
[1.0, 'welcome', 1.0, 'welcome', 1.0, 'welcome']
>>> print(list1 + list2)
[1.0, 'welcome', 'python', 'world']

成员检查

可用于判断数据元素(数据项)是否存在于列表中:

1
2
3
4
5
6
>>> list1 = [1.0, 'welcome']
>>> list2 = ["python", "world"]
>>> 'welcome' in list1
True
>>> 'welcome' not in list2
True

获取序列长度

len(list) 方法可返回列表元素(数据项)对象个数,也称为列表的长度或容量。

1
2
3
>>> list1 = [1, 2.0, "welcome", [1, 2, 3]]
>>> print(len(list1))
4

max(list) && min(list)

max(list)方法用于返回列表元素中的最大值,min(list)方法用于返回列表元素中的最小值。

1
2
3
4
5
>>> list1, list2 = ['Google', 'Baidu', '360'], [12, 100, 200]
>>> print ("list1 最大元素值 : ", max(list1))
list1 最大元素值 : Google
>>> print ("list2 最小元素值 : ", min(list2))
list2 最小元素值 : 12

注意,在获取列表最大或最小值时,必须保证列表元素对象类型一致。

1
2
3
4
5
6
7
>>> list1, list2 = ['Google', 'Baidu', '360'], [12, 100, "200"]
>>> print ("list1 最大元素值 : ", max(list1))
list1 最大元素值 : Google
>>> print ("list2 最小元素值 : ", min(list2))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'str' and 'int'

列表类型转换

前面,我们使用 list(Iterable) 内建函数来创建列表,使用它可以将其它可迭代对象转换为列表类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> aTuple = (123.321, 'Google', 'Baidu', '360')
>>> list1 = list(aTuple)
>>> print ("列表元素 : ", list1)
列表元素 : [123.321, 'Google', 'Baidu', '360']
>>> str="Hello World"
>>> list2=list(str)
>>> print ("列表元素 : ", list2)
列表元素 : ['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd']

>>> set1 = {'a', 'b', 'c'}
>>> dict1 = {'a':1, 'b':2}

>>> list(set1)
['b', 'a', 'c']
>>> list(dict1)
['a', 'b']

列表常用操作整理

由于列表是一个可变的序列对象,实际开发中,经常需要对 Python 列表进行查找和更新操作,包括向列表中添加元素、修改表中元素、删除元素以及进行元素查找。

列表添加元素

[1] >>>> 加法(列表连接)

+ 运算符可以将多个(同类型)序列连接起来,对于列表而言,相当于在第一个列表的末尾添加了另一个列表生成一个新的列表,原有的列表不会被改变:

1
2
3
4
5
6
7
8
9
10
>>> language = ["Python", "C++", "Java"]
>>> birthday = [1991, 1998, 1995]
>>> info = language + birthday

>>> print("language =", language)
language = ['Python', 'C++', 'Java']
>>> print("birthday =", birthday)
birthday = [1991, 1998, 1995]
>>> print("info =", info)
info = ['Python', 'C++', 'Java', 1991, 1998, 1995]

注意:+ 更多的是用来拼接列表,而且执行效率并不高,如果想在列表中插入元素,应该使用下面几个专门的方法。

[2] >>>> append(列表追加)

append() 方法用于在列表的末尾追加一个新元素对象,该方法的语法格式如下:

1
listname.append(obj)

注意,obj 表示到添加到列表末尾的数据,它可以是单个元素,也可以是列表、元组、集合、字典等。

1
2
3
4
>>> list = ['Google', 'Baidu', '360', "Google"]
>>> list.append("Opera")
>>> print ("更新后的列表 : ", list)
更新后的列表 : ['Google', 'Baidu', '360', 'Opera']

为什么要强调单个元素 >>>>

1
2
3
4
5
6
7
8
9
10
11
>>> list1 = [1,2]
>>> list1.append(3)
>>> print(list1)
[1,2,3]
>>> list1.append([4,5])
>>> print(list1)
[1,2,3,[4,5]]

# 下面添加了两个元素,会报错:
>>> list1.append(4,5)
TypeError: append() takes exactly one argument (2 given)

可以看到,当追加元素对象为列表、元组时,会将它们视为一个整体,作为一个元素添加到列表中,从而形成包含列表和元组的新列表。

[3] >>>> extend(列表扩展)

extend(iterable)append(obj) 的不同之处在于:extend(iterable) 不会把可迭代对象视为一个整体,而是把它们包含的元素逐个从列表末尾添加到列表中。

这里 iterable 可以是字符串、列表、元组、集合、字典等。若为字典,则仅会将键(key)作为元素依次添加至原列表的末尾。

1
2
3
4
5
>>> list1 = ['Google', 'Baidu', '360']
>>> list2 = list(range(5))
>>> list1.extend(list2)
>>> print("扩展后的列表:",list1)
扩展后的列表: ['Google', 'Baidu', '360', 0, 1, 2, 3, 4]

| >>>> =============================================================

深入理解列表中的 +extend()append() >>>>

+ 可以生成一个新的列表,而 extend(iterable)append(obj) 是在原列表的基础上操作的,不会生成新的列表,无返回值(None)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 定义一个包含多个类型的 list
list1 = [1, 4, 3.4, "yes", [1,2]]
print(list1, id(list1))

print("{:*^20}".format("1"))
# 比较 list 中添加元素的几种方法的用法和区别
list3 = [6,7]
l2 = list1 + list3
print(l2,id(l2))

print("{:*^20}".format("2"))
l2 = list1.extend(list3)
print(l2,id(l2))
print(list1,id(list1))

print("{:*^20}".format("3"))
l2 = list1.append(list3)
print(l2,id(l2))
print(list1,id(list1))

输出结果为:

1
2
3
4
5
6
7
8
9
[1, 4, 3.4, 'yes', [1, 2]] 140201757210184
*********1**********
[1, 4, 3.4, 'yes', [1, 2], 6, 7] 140201757213064
*********2**********
None 140201780587376
[1, 4, 3.4, 'yes', [1, 2], 6, 7] 140201757210184
*********3**********
None 140201780587376
[1, 4, 3.4, 'yes', [1, 2], 6, 7, [6, 7]] 140201757210184

可以看到,+ 可以生成一个新列表,而 extend()append() 是没有返回值(NoneType),所以千万不能放在等式的右侧(无意义),这是编程时常犯的错误,一定要引起注意!!!

============================================================= <<<< |

[4] >>>> insert(插入元素)

前面提到的,append() 和 extend() 方法只能在列表末尾插入元素,如果希望在列表中间某个位置插入元素,可以使用 insert(index, obj) 方法(插入的是元素对象):

1
listname.insert(index , obj)

其中,index 表示指定位置的索引值。insert() 会将 obj 元素对象插入到 listname 列表第 index 个元素的位置(相当于其它元素对象后移)。

1
2
3
4
5
6
7
8
9
10
>>> list4 = ['Python', 'C++', 'Java']
>>> id(list4[1])
1961084417456
>>> list4.insert(1, 'C')
>>> list4
['Python', 'C', 'C++', 'Java']
>>> id(list4[1])
1961052167280
>>> id(list4[2])
1961084417456

当插入列表或者元祖时,insert() 也会将它们视为一个整体,作为一个元素插入到列表中,这一点和 append() 是一样的。

1
2
3
4
5
6
7
8
>>> list4.insert(2, ['C#', 'JavaScript'])
>>> list4
['Python', 'C', ['C#', 'JavaScript'], 'C++', 'Java']
>>> lt5 = list4.insert(1, "test")
>>> lt5
>>> list4
['Python', 'test', 'C', ['C#', 'JavaScript'], 'C++', 'Java']
>>>

注意,和 extend()、append() 方法一样,insert() 也是没有返回值(NoneType),所以不要放在等式的右侧(无意义)。如果希望在列表的末尾追加元素,更建议使用 append() 和 extend()(insert() 不高效,)。


列表删除元素

Python 列表中删除元素主要分为以下 3 种场景:

  • 根据目标 元素所在位置的索引 进行删除,可以使用 del 关键字或者 pop() 方法;
  • 根据 元素本身的值 进行删除,可使用列表(list类型)提供的 remove() 方法;
  • 将列表中 所有元素全部删除,可使用列表(list类型)提供的 clear() 方法。

[1] >>>>> 根据元素索引删除元素

1)–> del

del 可以删除列表中的单个元素,格式为:

1
del listname[index]

例如:

1
2
3
4
5
6
7
8
9
lang = ["Python", "C++", "Java", "PHP", "Ruby", "MATLAB"]

# 使用正数索引
del lang[2]
print(lang)

# 使用负数索引
del lang[-2]
print(lang)

del 也可以删除中间一段连续的元素,格式为:

1
del listname[start : end]

其中,start 表示起始索引,end 表示结束索引。del 会删除从索引 start 到 end 之间的元素,不包括 end 位置的元素(前闭后开)。

例如:

1
2
3
4
5
6
7
8
lang = ["Python", "C++", "Java", "PHP", "Ruby", "MATLAB"]

del lang[1: 4]
print(lang)

lang.extend(["SQL", "C#", "Go"])
del lang[-5: -2]
print(lang)

| >>>> =============================================================

深入理解列表中的 del >>>>

一定要搞清楚 –> 删除的到底是变量还是数据?:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> #定义一个包含多个类型的 list
>>> list1 = [1,4,3.4,"yes",[1,2]]
>>> list2 = list1

>>> print(id(list2),id(list1))
1765451922248 1765451922248
>>> del list1
>>> print(list2)
[1, 4, 'hello', 3.4, 'yes', [1, 2]]
>>> print(list1)
Traceback (most recent call last):
File "C:\Users\mengma\Desktop\demo.py", line 8, in <module>
print(list1)
NameError: name 'list1' is not defined

另外,在实际过程中,即便使用 del 关键字删除了指定变量,且该变量所占用的内存再没有其他变量使用,此内存空间也不会真正地被系统回收并进行二次使用,它只是会被标记为无效内存。

如果想让系统回收这些可用的内存,需要借助 GC 库中的 collect() 函数。例如:

1
2
3
4
5
6
7
8
9
10
11
>>> import gc
>>> list1 = [1, 2, 3, 4, "yes"]

>>> del list1
>>> list1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'list1' is not defined

>>> gc.collect()
0

Python 中具有缓存重用机制,系统为了提升性能,会将一部分变量驻留在内存中。这个机制对于,多线程并发时程序产生大量占用内存的变量无法得到释放,或者某些不再需要使用的全局变量占用着大的内存,导致后续运行中出现内存不足的情况,此时就可以使用 del 关键字来回收内存,使系统的性能得以提升。同时,它可以为团队省去扩充大量内存的成本。

============================================================= >>>> |

2)–> pop([index=-1])

Python pop(index) 方法用来删除列表中指定索引处的元素,具体格式如下:

1
listname.pop(index=-1)

其中,如果指定 index 参数,默认会删除列表中的最后一个元素,类似于数据结构中的 “出栈” 操作。

用法举例:

1
2
3
4
5
6
7
8
9
10
11
>>> list1 = ['Google', 'Baidu', '360']
>>> list1.pop()
'360'
>>> print ("列表现在为 : ", list1)
列表现在为 : ['Google', 'Baidu']

>>> >>> list2 = list1.pop(1)
>>> print ("列表现在为 : ", list1)
列表现在为 : ['Google']
>>> list2
'Baidu'

pop() 函数有返回值,意味着 “出栈” 元素是可获取。

大部分编程语言都会提供和 pop() 相对应的方法,就是 push(),该方法用来将元素添加到列表的尾部,类似于数据结构中的“入栈”操作。但是 Python 是个例外,Python 并没有提供 push() 方法,因为完全可以使用 append() 来代替 push() 的功能。


[2] >>>>> 根据元素值删除元素

remove(obj) 函数可用于移除列表中某个值的第一个匹配项。而且必须保证该元素是存在的,否则会引发 ValueError 错误。

1
2
3
4
5
6
7
8
9
10
11
12
>>> list1 = ['Google', 'Baidu', '360', "Google"]
>>> list1.remove("Google")
>>> print ("列表现在为 : ", list1)
列表现在为 : ['Baidu', '360', 'Google']
>>> list1.remove("Google")
>>> print ("列表现在为 : ", list1)
列表现在为 : ['Baidu', '360']

>>> list1.remove("Google")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list

remove(obj) 函数无返回值,可用来进行列表元素对象过滤。


[3] >>>>> 删除列表所有元素

clear() 函数可用于删除列表的所有元素(清空列表),类似于 **del a[:]**。

1
2
3
4
>>> list = ['Google', 'Baidu', '360']
>>> list.clear()
>>> print("清空后的列表:",list)
清空后的列表: []

列表修改元素

Python 提供了两种修改列表(list)元素的方法,你可以每次修改单个元素,也可以每次修改一组元素(多个)。

[1] >>>>> 修改单个元素

修改单个元素非常简单,直接对元素进行赋值即可:

1
2
3
4
5
6
7
8
>>> list = ['Google', 'Baidu', '360', 999]
>>> print ("第三个元素为 : ", list[2])
第三个元素为 : 360
>>> list[3] = 1999
>>> print ("更新后的第三个元素为 : ", list[2])
更新后的第三个元素为 : 360
>>> print(list)
>>> ['Google', 'Baidu', '360', 1999]

[2] >>>>> 修改一组元素

Python 支持通过切片(slice)语法给一组元素赋值,并且是一组值(单个值会报错)。

1
2
3
4
nums = [40, 36, 89, 2, 36, 100, 7]
# 修改第 1~4 个元素的值(不包括第4个元素)
nums[1: 4] = [45.25, -77, -52.5]
print(nums)

如果对空切片(slice)赋值,就相当于插入一组新的元素:

1
2
3
4
nums = [40, 36, 89, 2, 36, 100, 7]
# 在 4 个位置插入元素
nums[4: 4] = [-77, -52.5, 999]
print(nums)

注意,如果不指定步长(step 参数),Python 就不要求新赋值的元素个数与原来的元素个数相同;这意味,该操作既可以为列表添加元素,也可以为列表删除元素。

1
2
3
4
5
6
7
8
9
>>> nums = [40, 36, 89, 2, 36, 100, 7]
>>> nums[1:4] = 77
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only assign an iterable

>>> nums[1:4] = [77, 1]
>>>> nums
[40, 77, 1, 36, 100, 7]

列表查找元素

Python 列表(list)提供了 index() 和 count() 方法,它们都可以用来查找元素。

[1] >>>>> 元素数目查询

count() 方法用来统计某个元素在列表中出现的次数,基本语法格式为:

1
listname.count(obj)

其中,obj 表示要统计的元素对象。如果 count() 返回 0,就表示列表中不存在该元素,所以 count() 也可以用来判断列表中的某个元素是否存在。

1
2
3
4
5
6
7
8
9
10
11
12
>>> nums = [40, 36, 89, 2, 36, 100, 7, -20.5, 36]

>>> # 统计元素出现的次数
>>> print("36 出现了 %d 次" % nums.count(36))
36 出现了 3 次

>>> # 判断一个元素是否存在
>>> if nums.count(100):
>>> print("列表中存在 100 这个元素")
>>> else:
>>> print("列表中不存在 100 这个元素")
列表中存在 100 这个元素

[2] >>>>> 元素索引查询

list.index() 函数可以用于从列表中找出某个值第一个匹配项的索引位置,如果没有找到对象则抛出异常。所以在查找之前最好使用 count() 方法或者 in && not in 判断一下是否存在。

index() 的语法格式为:

1
listname.index(obj, start, end)

其中,obj 表示要查找的元素,并且你可以限定查找范围:start 表示起始位置,end 表示结束位置。

1
2
3
4
5
6
7
>>> list = ['Google', 'Baidu', '360']
>>> list.index("Baidu")
1
>>> list.index("Opera")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: 'Opera' is not in list

其它常用操作

[1] >>>>> list.reverse()

reverse(list) 函数用于反向列表中元素。没有返回值,但是会对列表的元素进行反向排序。

1
2
3
4
>>> list = ['Google', 'Baidu', '360']
>>> list.reverse()
>>> print ("列表反转后: ", list)
列表反转后: ['360', 'Baidu', 'Google']

这里还可以使用序列中的内置函数 reversed(seq),其功能是对于给定的序列(包括列表、元组、字符串以及 range(n) 区间),该函数可以返回一个逆序序列的迭代器(用于遍历该逆序序列)。

反向遍历列表的方法(不影响原列表) >>>>

1
2
3
4
5
6
7
8
9
10
11
12
13
list1 = [1, 2, 3, 4]

# Method 1:
for item in list1[::-1]:
print(item)

# Method 2:
for item in range(len(list1) - 1, -1, -1):
print(list1[item])

# Method 3:
for item in reversed(list1):
print(item)

但还是尽量使用迭代器吧,数据量很大的时候不用迭代器的话有可能会占用过多的内存。


[2] >>>>> list.sort( key=None, reverse=False)

sort() 函数用于对原列表进行排序。该方法没有返回值,但是会对列表的对象进行排序。参数说明:

  • key:指定用来进行比较的元素。取自于可迭代对象中,指定可迭代对象中的哪一个元素来进行排序。
  • reverse:定义排序规则,reverse = True 降序, reverse = False 升序(默认)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> list = [1, 2, 3, 4]
>>> list.sort()
>>> print ( "List : ", list)
List : [1, 2, 3, 4]
>>> list.sort(reverse=True)
>>> print ( "List : ", list)
List : [4, 3, 2, 1]

# 获取列表的第二个元素
>>> def takeSecond(elem):
... return elem[1]
...
>>> # 列表
>>> random = [(2, 2), (3, 4), (4, 1), (1, 3)]
# 指定第二个元素排序
>>> random.sort(key=takeSecond)
# 输出类别
>>> print ('排序列表:', random)
排序列表: [(4, 1), (2, 2), (1, 3), (3, 4)]

[3] >>>>> list.copy()

copy() 函数用于复制列表(深、浅拷贝混合),返回复制后的新列表。

1
2
3
4
5
6
7
8
9
10
11
>>> list1 = ['Google', (1,2), [1, 2]]
>>> list2 = list1.copy()
>>> print("List2:",list2)
List2: ['Google', 'Baidu', '360']

>>> print(id(list1), id(list2))
1961085147392 1961085159808
>>> print(id(list1[1]), id(list2[1]))
1961084342016 1961084342016
>>> print(id(list1[1][1]), id(list2[1][1]))
140724350033696 140724350033696

Python 中栈和队列的实现

我们知道,队列(Queue [kju])和栈(Stack)是两种数据结构,其内部都是按照固定顺序来存放变量的,二者的区别在于对数据的存、取顺序:

  • 队列是,先存入的数据最先取出,即“先进先出”;
  • 栈是,最后存入的数据最先取出,即“后进先出”。

List 实现栈和队列

Python 中list 类型数据本身的存放就是有顺序的,而且内部元素又可以是各不相同的类型,非常适合用于队列和栈的实现。

[1] >>>> List 实现队列

使用 list 列表模拟队列功能的实现方法是:定义一个 list 变量,存入数据时使用 insert() 方法,设置其第一个参数为 0,即表示每次都从最前面插入数据;读取数据时,使用 pop() 方法,即将队列的最后一个元素弹出。

如此 list 列表中数据的存取顺序就符合 “先进先出” 的特点。实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
# 定义一个空列表,当做队列
queue = []

# 向列表中插入元素
queue.insert(0,1)
queue.insert(0,2)
queue.insert(0,"hello")

print(queue)
print("取一个元素:",queue.pop())
print("取一个元素:",queue.pop())
print("取一个元素:",queue.pop())

[2] >>>> List 实现栈

使用 list 列表模拟栈功能的实现方法是,使用 append() 方法存入数据;使用 pop() 方法读取数据。

如此 list 列表中数据的存取顺序就符合“先进先出”的特点。实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
# 定义一个空 list 当做栈
stack = []

stack.append(1)
stack.append(2)
stack.append("hello")
print(stack)

print("取一个元素:",stack.pop())
print("取一个元素:",stack.pop())
print("取一个元素:",stack.pop())

collections 实现栈和队列

List 中实现队列的方法中,插入数据的部分是通过 insert() 方法实现的,这种方法效率并不高,因为每次从列表的开头插入一个数据,列表中所有元素都得向后移动一个位置。

一个相对更高效的方法,即使用标准库的 collections 模块中的 deque(double-end queue) 结构体,它被设计成在两端存入和读取都很快的特殊 list,可以用来实现栈和队列的功能。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
queueAndStack = deque()
queueAndStack.append(1)
queueAndStack.append(2)
queueAndStack.append("hello")
print(list(queueAndStack))

#实现队列功能,从队列中取一个元素,根据先进先出原则,这里应输出 1
print(queueAndStack.popleft())

#实现栈功能,从栈里取一个元素,根据后进先出原则,这里应输出 hello
print(queueAndStack.pop())

#再次打印列表
print(list(queueAndStack))

元组(Tuple)

元组与列表是非常类似的(序列),区别在于元组的元素值不能修改(不可变数据类型),元组是放在括号中,列表是放于方括号中。

元组的创建

Python 提供了两种创建元组的方法:

[1] >>>> 元组的标准创建格式:

元组创建也很简单,只需要在小括号中添加元素(数据项),并使用逗号隔开即可:

1
2
3
4
>>> tup1 = ('Google', 'Baidu', '360')
>>> tup2 = 'welcome', 'python', 'world', 1996, 123.321;
>>> type(tup2)
<class 'tuple'>

注意,当元组中 只包含一个元素 时,需要在元素后面添加逗号,否则括号会被当作运算符使用

1
2
3
4
5
6
7
>>>tup1 = (50)
>>> type(tup1) # 不加逗号,类型为整型
<class 'int'>

>>> tup1 = (50,)
>>> type(tup1) # 加上逗号,类型为元组
<class 'tuple'>

同理,元组中的元素类型也可以是不同的数据类型(数字类型、字符串、元组、列表、字典等):

1
2
3
>>> tuple1 = ('tuple test', 123.321, [1,2], {"name":1,"age":2}, (1,2,"test"))
>>> print(type(tuple1), tuple1)
<class 'tuple'> ('tuple test', 123.321, [1, 2], {'name': 1, 'age': 2}, (1, 2, 'test'))

不规范写法 >>>>

Python 中,元组通常都是使用一对小括号将所有元素包围起来的,但小括号不是必须的,只要将各元素用逗号隔开,Python 就会将其视为元组:

1
2
3
>>> tup1 = '123', '456'
>>> print(tup1, type(tup1))
('123', '456') <class 'tuple'>

空元组 >>>>

1
2
3
>>> tup1 = ()
>>> type(tup1)
<class 'tuple'>

[2] >>>> tuple(Iterable) 内建函数创建列表:

除了使用 () 创建列表外,Python 还提供了一个内置的函数 tuple(Iterable),使用它可以将其它可迭代对象转换为元组类型。

关于 Python 中可迭代对象的说明请参见博文:Python 中的可迭代对象(Iterable)

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 将字符串转换成元组
tup1 = tuple("hello")
print(tup1)

# 将列表转换成元组
list1 = ['Python', 'Java', 'C++', 'JavaScript']
tup2 = tuple(list1)
print(tup2)

# 将字典转换成元组
dict1 = {'a':100, 'b':42, 'c':9}
tup3 = tuple(dict1)
print(tup3)

# 将区间转换成元组
range1 = range(1, 6)
tup4 = tuple(range1)
print(tup4)

# 将推导式转换成元组
tup5 = tuple(x for x in "abcdefgh")
print(tup4)

# 创建空元组
print(tuple())

运行结果如下:

1
2
3
4
5
('h', 'e', 'l', 'l', 'o')
('Python', 'Java', 'C++', 'JavaScript')
('a', 'b', 'c')
(1, 2, 3, 4, 5)
()

序列支持的操作

由于元组(tuple)属于 Python 的内建序列,故序列(seq)中包含的基本操作 tuple也具有。例如:1.索引、2.切片、3.加、4.乘、5.成员检查、6.获取序列的长度、7.取序列中的最大、最小值等。

–> 字符串和元组

相较于列表,字符串(str)可执行操作要更接近于元组(tuple),这是由于两者都是属于不可变数据类型导致。但是我们要注意,尽管 tuple 元素不可改变,但它可以包含可变对象,比如列表、字典等。

Python 中的元组是一个不可变对象,所以所有修改和生成元组的操作的实现方法,都是在另一个内存片段中生成一个新字符串对象。

元组索引和切片

[1] >>>> 访问单个元组元素

类似于列表,元组中,可以用索引来访问 list 中每一个对应位置的元素。索引是从 0 开始的(-1 为从末尾的开始位置的逆向索引):

1
2
3
4
5
6
7
8
9
10
11
>>> website = ('Google', 'Baidu', '360')
>>> len(website)
3
>>> website[0]
'Google'
>>> website[1]
'Baidu'
>>> website[-1]
'360'
>>> website[-2]
'Baidu'

对于嵌套元组索引:

1
2
3
4
5
6
7
>>> tup1 = ('str1', 'str2', ['list_element1', 'list_element2'])
>>> tup1[0]
'str1'
>>> tup1[2][0]
'list_element1'
>>> tup1[2][1]
'list_element2'

注意,当索引超出了 tuple 范围时,Python 会报一个IndexError错误,所以,要确保索引不要越界。

1
2
3
4
>>> website[3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: tuple index out of range

[2] >>>> 切片

与列表的分割(切片)一样,我们同样可以通过[]:对元组进行切片操作:

1
2
3
>>> website = ('Google', 'Baidu', '360')
>>> website[0:len(classmates)]
('Google', 'Baidu', '360')

和字符串一样,我们甚至可以指定切片的步长:

1
2
3
4
5
>>> website = ('Google', 'Baidu', '360')
>>> website[0:len(classmates):1]
('Google', 'Baidu', '360')
>>> website[0:len(classmates):2]
('Google', '360')

元组拼接(+)和重复(*)

1
2
3
4
5
6
>>> tup1 = (1.0, 'welcome')
>>> tup2 = ("python", "world")
>>> print(tup1 * 3)
(1.0, 'welcome', 1.0, 'welcome', 1.0, 'welcome')
>>> print(tup1 + tup2)
(1.0, 'welcome', 'python', 'world')

成员检查

可用于判断数据元素(数据项)是否存在于列表中:

1
2
3
4
5
6
>>> tup1 = (1.0, 'welcome')
>>> tup2 = ("python", "world")
>>> 'welcome' in tup1
True
>>> 'welcome' not in tup2
True

获取序列长度

len(tuple) 方法可返回元组元素(数据项)个数,也称为元组的长度或容量。

1
2
3
>>> tup1 = ('Google', 'Baidu', '360')
>>> print(len(tup1))
3

max(tuple) && min(tuple)

max(tuple)方法用于返回元组元素中的最大值,min(tuple)方法用于返回元组元素中的最小值。

1
2
3
4
5
>>> tup1, tup2 = ('Google', 'Baidu', '360'), (12, 100, 200)
>>> print ("tup1 最大元素值 : ", max(tup1))
tup1 最大元素值 : Google
>>> print ("tup2 最小元素值 : ", min(tup2))
tup2 最小元素值 : 12

注意,在获取元组最大或最小值时,必须保证元组元素类型一致。

1
2
3
4
5
6
7
>>> tup1, tup2 = ('Google', 'Baidu', '360'), (12, 100, '200')
>>> print ("tup1 最大元素值 : ", max(tup1))
tup1 最大元素值 : Google
>>> print ("tup2 最小元素值 : ", min(tup2))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'str' and 'int'

元组类型转换

前面,我们使用 tuple(Iterable) 内建函数来创建列表,使用它可以将其它可迭代对象转换为列表类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> list1 = [123.321, 'Google', 'Baidu', '360']
>>> tup1 = tuple(list1)
>>> print ("元组元素 : ", tup1)
元组元素 : (123.321, 'Google', 'Baidu', '360')

>>> str="Hello World"
>>> tup2=tuple(str)
>>> print ("元组元素 : ", tup2)
元组元素 : ('H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd')

>>> set1 = {'a', 'b', 'c'}
>>> dict1 = {'a':1, 'b':2}

>>> tuple(set1)
('b', 'a', 'c')
>>> tuple(dict1)
('a', 'b')

元组的更新与删除

正如我们之前说的,和列表不同的是,tuple 是不可变数据类型,其元素值是不允许修改的。

所以,和字符串中一样,修改元组元素操作是非法的:

1
2
3
4
5
>>> website = ('Google', 'Baidu', '360')
>>> website[2] = "Opera"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

同理,元组中的元素值是不允许删除的,但我们可以使用 del 语句来删除整个元组(引用):

1
2
3
4
5
6
7
8
9
10
11
>>> website = ('Google', 'Baidu', '360')
>>> del website[2]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object doesn't support item deletion

>>> del website
>>> print(website)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'website' is not defined

“可变的” tuple ??? >>>>

但是我们要注意:尽管 tuple 元素不可改变,但它可以包含可变对象,比如 list、set、dict等。

先来给出一个样例:

1
2
3
4
5
>>> tup1 = ('a', 'b', ['A', 'B'])
>>> tup1[2][0] = 'E'
>>> tup1[2][1] = 'F'
>>> print(tup1)
('a', 'b', ['E', 'F'])

哎?之前不是说 tuple一旦定义后就不可变了吗?怎么后来又变了?

表面上看,tuple 的元素确实变了,但其实变的不是 tuple 的元素,而是 list 的元素。tuple 一开始指向的 list 并没有改成别的 list。所以,tuple 所谓的“不变”是说,tuple 的每个元素,指向永远不变。即指向'a',就不能改成指向'b',指向一个 list,就不能改成指向其他对象,但指向的这个 list 本身是可变的!!!


深入了解 List && Tuple

这一小节内容可以解释为什么我们会将 List 和 Tuple 放在一篇博文里面阐述。

元组和列表同属序列类型,且都可以按照特定顺序存放一组数据,数据类型不受限制,只要是 Python 支持的数据类型就可以。

元组和列表最大的区别就是 >>>> 元组是一个不可变对象,而列表是一个可变对象

你可以理解为:tuple 元组是一个只读版本的 list 列表。

需要注意的是,这样的差异势必会反应到两者的存储方式上:

[1] >>>> List

先研究一下 Python 中 List 的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)),则会向系统请求更大的内存空间,并把原来的元素全部拷贝过去。

对于 tuple,它是不可变的,意味着元组长度大小固定,且存储元素不可变,所以存储空间也是固定的。

[2] >>>> Tuple

再来看看 Python 中 tuple 的结构:

1
2
3
4
5
6
7
8
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 会在后台,对静态数据做一些资源缓存(传送门)。通常来说,因为垃圾回收机制的存在,如果一些变量不被使用了,Python 就会回收它们所占用的内存,返还给操作系统,以便其他变量或其他应用使用。

但是对于一些静态变量(比如元组),如果它不被使用并且占用空间不大时,Python 会暂时缓存这部分内存。这样的话,当下次再创建同样大小的元组时,Python 就可以不用再向操作系统发出请求去寻找内存,而是可以直接分配之前缓存的内存空间,这样就能大大加快程序的运行速度。

[3] >>>> Tuple VS List

计算初始化一个相同元素的列表和元组分别所需的时间。我们可以看到,元组的初始化速度要比列表快 5 倍:

1
2
3
4
C:\Users\xxxxxx>python -m timeit 'x=(1,2,3,4,5,6)'
20000000 loops, best of 5: 9.97 nsec per loop
C:\Users\xxxxxx>python -m timeit 'x=[1,2,3,4,5,6]'
5000000 loops, best of 5: 50.1 nsec per loop

当然,如果你想要增加、删减或者改变元素,那么列表显然更优。因为对于元组来说,必须得通过新建一个元组来完成。

元组确实没有列表那么多功能,但是元组依旧是很重要的序列类型之一,元组的不可替代性体现在以下这些场景中:

  • 元组作为很多内置函数和序列类型方法的返回值存在,也就是说,在使用某些函数或者方法时,它的返回值会元组类型,因此你必须对元组进行处理;
  • 元组比列表的访问和处理速度更快,因此,当需要对指定元素进行访问,且不涉及修改元素的操作时,建议使用元组;
  • 元组可以在映射(和集合的成员)中当做“键”使用,而列表不行。

可变和不可变对象

前面我们一直在说,元组(tuple)和列表(list)很相似,但元组是可变的,列表不可变。

关于 Python 中的可变和不可不对象内容解读参见:Python 中的可变对象和不可变对象

当然,如果不理解的话,可以暂时将其当成一个黑箱子,学过集合和字典后再来看会更加顺利。


Author

Waldeinsamkeit

Posted on

2018-01-06

Updated on

2022-04-04

Licensed under

You need to set install_url to use ShareThis. Please set it in _config.yml.

Comments

You forgot to set the shortname for Disqus. Please set it in _config.yml.