• Python学习笔记 - 数据结构:元组、列表、集合和字典


    前言

            在计算机程序中会有很多数据,这些数据通常需要一个容器将它们管理起来,这就是数据结构,常见的有:数组(Array)、集合(Set)、列表(List)、队列(Queue)、链表(LinkedList)、树(Tree)、堆(Heap)、栈(Stack) 和 字典(Dict) 等数据结构。

            在Python中内置的数据结构主要有:元组(tuple)、列表(list)、集合(set) 和 字典(dict)。这些数据结构都可用于保存多个数据项,这对于编程而言是非常重要的——因为程序不仅需要使用单个变量来保存数据,还需要使用多种数据结构来保存大量数据,而元组、列表、集合和字典就可以满足保存大量数据的需求。

    注意Python中并没有数组数据结构,因为数组要求元素类型是一致的。而Python作为动态类型语言,不强制声明变量的数据类型,也不强制检查元素的数据类型,不能保证元素的数据类型一致,所以Python中没有数组结构。

    一、序列

    所谓序列,指的是一种包含多项数据的数据结构,序列包含的多个数据项(也叫成员或元素)按顺序排列,可通过索引来访问成员。

    序列的特点是:可迭代的、元素有序、可以重复出现。

    1.1 Python的序列

    Python 的常见序列类型包括字符串(str)、元组(tuple)、列表(list)、范围(range) 和 字节序列(bytes)。

    序列可进行的操作有:索引、分片、加 和 乘。

    1.2 序列操作

    1、索引操作

    序列中第一个元素的索引是0,其他元素的索引是第一个元素的偏移量。可以有正偏移量,称为正值索引;也可以有福偏移量,称为负值索引。正值索引的最后一个元素索引是 “序列长度 - 1”。负值索引最后一个元素索引是 “-1”。

    例如,“Hello” 字符串,它的正值索引如下图1-1(a) 所示,负值索引如图1-1(b)所示。

    图1  正负索引示例

    序列中的元素是通过索引下标访问的,即中括号 [index] 方式访问。在 Python Shell 中运行示例如下:

    1. >>> a = 'Hello'
    2. >>> a[0]
    3. 'H'
    4. >>> a[1]
    5. 'e'
    6. >>> a[4]
    7. 'o'
    8. >>> a[-1]
    9. 'o'
    10. >>> a[-2]
    11. 'l'
    12. >>> a[5]
    13. Traceback (most recent call last):
    14. File "<stdin>", line 1, in <module>
    15. IndexError: string index out of range
    16. >>> max(a)
    17. 'o'
    18. >>> min(a)
    19. 'H'
    20. >>> len(a)
    21. 5

    a[0] 是所访问序列的第一个元素,最后一个元素的索引可以是 4 或 -1。但是索引超过范围,则会发生 IndexError 错误。另外,获取序列的长度使用函数 len(),类似的序列还有 max() 和 min() 函数,max() 函数返回最后一个元素,min() 函数返回第一个元素

    2、序列的加和乘操作

    Python 的 “+” 和 “*” 运算符可以应用于序列。+ 运算符可以将两个序列连接起来,* 运算符可以将序列重复多次。

    在 Python Shell 中运行示例如下:

    1. >>> a = 'Hello'
    2. >>> a * 3
    3. 'HelloHelloHello'
    4. >>> print(a)
    5. Hello
    6. >>> a += ' '
    7. >>> a += 'World'
    8. >>> print(a)
    9. Hello World

    3、序列分片操作

    序列的分片(Slicing) 就是从序列中切分出小的子序列。分片使用分片运算符,分片运算符有两种形式。

    • [start : end]  start 是开始索引,end 是结束索引。
    • [start : end : step]  start 是开始索引,end 是结束索引,step 是步长,步长是在分片时获取元素的间隔。步长可以是正整数,也可以是负整数。

    注意》切下的分片包括 start 索引位置元素,但不包括 end 索引位置元素,start 和 end 也都可以忽略。

    在 Python Shell 中运行示例代码如下:

    1. >>> a = 'Hello'
    2. >>> a[1:3]
    3. 'el'
    4. >>> a[:3]
    5. 'Hel'
    6. >>> a[0:]
    7. 'Hello'
    8. >>> a[:]
    9. 'Hello'
    10. >>> a[1:-1]
    11. 'ell'

    (1)上述代码表达式 a[1:3] 是切出 1~3 的子字符串,注意不包括索引3的元素,所以结果是 "el"。

    (2)表达式 a[:3] 省略了开始索引,则默认开始索引是 0,所以 a[:3] 与 a[0:3] 分片结果是一样的。

    (3)表达式 a[0:] 省略了结束索引,默认结束索引是序列的长度,即 5,所以 a[0:] 与 a[0:5] 的分片结果是一样的。

    (4)表达式 a[:] 同时省略了开始索引和结束索引,a[:] 与 a[0:5] 的结果是一样的。

    (5)表达式 a[1:-1] 使用了负值索引,对照上图 1-1,不难计算出 a[1:-1] 的结果是 "ell"。

    分片时使用 [start : end: step] 可以指定步长(step),步长与当次元素索引、下次元素索引之间的关系如下:

    下次元素索引 = 当次元素索引 + 步长

    在 Python Shell 中运行示例代码如下:

    1. >>> a = 'Hello'
    2. >>> a[1:5]
    3. 'ello'
    4. >>> a[1:5:2]
    5. 'el'
    6. >>> a[0:3]
    7. 'Hel'
    8. >>> a[0:3:2]
    9. 'Hl'
    10. >>> a[0:3:3]
    11. 'H'
    12. >>> a[::-1]
    13. 'olleH'

    (1)表达式 a[1:5] 省略了步长参数,步长默认值是1,结果是 "ello"。

    (2)表达式 a[1:5:2] 步长为2,结果是 "el"。

    (3)表达式 a[0:3] 分片后的子字符串是 "Hel",而 a[0:3:3] 步长为 3,分片结果为 "H" 字符。

    (4)当步长为负数时比较麻烦,负数时是从右向左获取元素,所以表达式 a[::-1] 分片的结果是原始字符串的逆置,结果为 "olleH"。

    二、元组

    2.1 创建元组

    元组(tuple) 是一种不可变序列,一旦创建就不能修改。创建元素可以使用 tuple([iterable]) 函数或者直接用逗号 “,” 将元素分隔。

    在Python Shell 中运行示例代码如下:

    1. >>> 21,32,43,45 # --1
    2. (21, 32, 43, 45)
    3. >>> (21,32,43,45) # --2
    4. (21, 32, 43, 45)
    5. >>> a = (21,32,43,45)
    6. >>> print(a)
    7. (21, 32, 43, 45)
    8. >>> ('Hello', 'World') # --3
    9. ('Hello', 'World')
    10. >>> ('Hello', 'World', 1, 2, 3) # --4
    11. ('Hello', 'World', 1, 2, 3)
    12. >>> tuple([21,32,43,45]) # --5
    13. (21, 32, 43, 45)

    (1)代码第1处创建了一个有 4 个元素的元组,创建元组时使用小括号把元素括起来不是必须的,但是还是建议带上。

    (2)代码第2处使用小括号将元素括起来,这只是为了提高程序的可读性。

    (3)代码第3处创建了一个字符串元组

    (4)代码第4处创建了字符串和整数混合的元组。Python 中没有强制声明数据类型,因此元组中的元素可以是任何数据类型。

    另外,元组还可以通过 tuple() 函数创建,参数 iterable 可以是任何可迭代对象。

    (5)代码第5处使用了 tuple() 函数创建元组对象,实参 [21, 32, 43, 45] 是一个列表,列表是可迭代对象,可以作为 tuple() 函数参数创建元组对象。

    创建元组还需要注意如下极端情况:

    1. >>> a = (21)
    2. >>> type(a)
    3. <class 'int'>
    4. >>> a = (21,)
    5. >>> type(a)
    6. <class 'tuple'>
    7. >>> a = ()
    8. >>> type(a)
    9. <class 'tuple'>

    从上述代码可见,当一个元组只有一个元素时,后面的逗号(,) 是不能省略的,即 (21, ) 表示的是只有一个元素的元组,而 (21) 表示的是一个整数。另外,() 可以创建空元组对象。

    2.2 访问元组

    元组作为序列,可以通过下标索引的方式访问其中的元素,也可以对其进行分片。

    在Python Shell 中运行示例代码如下:

    1. >>> a = ('Hello', 'World', 1, 2, 3) # --1
    2. >>> a[1]
    3. 'World'
    4. >>> a[1:3]
    5. ('World', 1)
    6. >>> a[2:]
    7. (1, 2, 3)
    8. >>> a[:2]
    9. ('Hello', 'World')

    (1)上述代码第1处是元组 a,a[1] 是访问元组下标索引为 1 的元素,即元组的第二个元素。表达式 a[1:3]、a[2:] 和 a[:2] 都是分片操作。

    元组还可以进行拆包(Unpack)操作,就是将元组的元素取出赋值给不同的变量。

    在 Python Shell 中运行示例代码如下:

    1. >>> a = ('Hello', 'World', 1, 2, 3)
    2. >>> str1, str2, n1, n2, n3 = a # --1
    3. >>> str1
    4. 'Hello'
    5. >>> str2
    6. 'World'
    7. >>> n1
    8. 1
    9. >>> n2
    10. 2
    11. >>> n3
    12. 3
    13. >>> str1, str2, *n = a # --2
    14. >>> str1
    15. 'Hello'
    16. >>> str2
    17. 'World'
    18. >>> n
    19. [1, 2, 3]
    20. >>> str1, _, n1, n2, _ = a # --3
    21. >>> str1
    22. 'Hello'
    23. >>> n1
    24. 1
    25. >>> n2
    26. 2

    (1)上述代码第1处是将元组 a 进行拆包操作,接收拆包元素的变量个数应等于元组中的元素个数。接收变量个数可以少于元组的元素个数。

    (2)代码第2处接收变量只有 3 个,最后一个很特殊,变量 n 前面有星号,表示将剩下的元素作为一个列表赋值给变量 n。

    (3)另外,还可以使用下划线指定不取值哪些元素,代码第3处表示不取第二个和第五个元素。

    2.3 遍历元组

    一般使用 for 循环遍历元组,示例代码如下:程序清单:traverse_tuple.py

    1. # coding=utf-8
    2. # 代码文件: 元组/traverse_tuple.py
    3. # 使用for循环遍历元组
    4. a = (21, 32, 43, 45)
    5. for item in a: # --1
    6. print(item)
    7. print('--------------')
    8. for i, item in enumerate(a): # --2
    9. print('{0} -- {1}'.format(i, item))
    10. # print('%d -- %d' %(i, item))

    运行结果:

    1. >python traverse_tuple.py
    2. 21
    3. 32
    4. 43
    5. 45
    6. --------------
    7. 0 -- 21
    8. 1 -- 32
    9. 2 -- 43
    10. 3 -- 45

    一般情况下,遍历的目的只是取出每一个元素值,见代码第1处的 for 循环。但有时候需要在遍历过程中同时获取索引,这时可以使用代码第2处的 for 循环,其中 enumerate(a) 函数可以获得一个元组对象,该元组对象有两个成员,第一个成员是索引,第二个成员是数值。所以 (i, item) 是元组拆包过程,其中变量 i 是元组 a 的当前索引,item 是元组 a 的当前元素值。

    注意》本节所介绍的元组遍历方式适合于所有序列,如字符串、范围 和 列表等。

    三、列表

    列表(list) 也是一种数据结构,与元组不同的是,列表具有可变性,可以追加、插入、删除和替换列表中的元素。

    3.1 创建列表

    创建列表可以使用 list([iterable]) 函数,或者用中括号 [ ] 将元素括起来,元素之间用逗号分隔。

    在Python Shell 中运行示例代码如下:

    1. >>> [20, 10, 50, 40, 30] # --1
    2. [20, 10, 50, 40, 30]
    3. >>> []
    4. []
    5. >>> ['Hello', 'World', 1, 2, 3] # --2
    6. ['Hello', 'World', 1, 2, 3]
    7. >>> a = [10] # --3
    8. >>> type(a)
    9. <class 'list'>
    10. >>> a = [10,] # --4
    11. >>> type(a)
    12. <class 'list'>
    13. >>> list((20, 10, 50 ,40, 30)) # --5
    14. [20, 10, 50, 40, 30]

    (1)上述代码第1处创建了一个有5个元素的列表,注意中括号不能省略,如果省略了中括号,那就变成元组了。

    (2)创建空列表是 [ ] 表达式,列表中可以放入任何对象,代码第2处是创建一个字符串和整数混合的列表。

    (3)代码第3处是创建只有一个元素的列表,中括号不能省略。

    (4)另外,无论是元组还是列表,每一个元素后面都跟着一个逗号,只是最后一个元素的逗号经常是省略的,代码第4处最后一个元素没有省略逗号。

    (5)另外,列表还可以通过 list([iterable]) 函数创建,参数 iterable 是任何可迭代对象。代码第5处使用 list() 函数创建列表对象,实参 (20, 10, 50, 40, 30) 是一个元组,元组是可迭代对象,可以作为 list() 函数参数创建列表对象。

    3.2 追加元素

    列表中追加单个元素可以使用 append() 方法。如果想追加另一个列表,可以使用 + 运算符或 extend() 方法。

    append() 方法语法:list.append(x)

    其中,参数 x 是要追加的单个元素值。

    extend() 方法语法:list.extend(t)

    其中,参数 t 是要追加的另外一个列表。

    在 Python Shell 中运行示例代码如下:

    1. >>> student_list = ['张三', '李四', '王五']
    2. >>> student_list.append('董六') # --1
    3. >>> student_list
    4. ['张三', '李四', '王五', '董六']
    5. >>> student_list
    6. ['张三', '李四', '王五', '董六']
    7. >>> student_list += ['刘备', '关羽'] # --2
    8. >>> student_list
    9. ['张三', '李四', '王五', '董六', '刘备', '关羽']
    10. >>> student_list.extend(['张飞', '赵云']) # --3
    11. >>> student_list
    12. ['张三', '李四', '王五', '董六', '刘备', '关羽', '张飞', '赵云']

    (1)上述代码中第1处使用了 append() 方法,在列表后面追加了一个元素,append() 方法不能同时追加多个元素。

    (2)代码第2处利用 += 运算符追加多个元素,能够支持 += 运算是因为列表支持 + 运算。

    (3)代码第3处使用了 extend() 方法追加多个元素,以列表作为函数实参。

    3.3 插入元素

    插入元素可以使用列表的 insert() 方法,该方法可以在指定索引位置插入一个元素。

    insert() 方法语法:list.insert(i, x)

    其中参数 i 是要插入的索引位置,参数 x 是要插入的元素数值。

    在 Python Shell 中运行示例代码如下:

    1. >>> student_list = ['张三', '李四', '王五']
    2. >>> student_list.insert(2, '刘备')
    3. >>> student_list
    4. ['张三', '李四', '刘备', '王五']

    上述代码中,student_list 调用 insert() 方法,在索引 2 位置插入一个元素,新元素的索引为2。

    3.4 替换元素

    列表具体可变性,其中的元素替换很简单,通过列表下标将索引元素放在赋值符号 “=” 左边,进行赋值即可替换。

    在Python Shell 中运行示例代码如下:

    1. >>> student_list = ['张三', '李四', '王五']
    2. >>> student_list[0] = '诸葛亮'
    3. >>> student_list
    4. ['诸葛亮', '李四', '王五']

    其中,student_list[0] = "诸葛亮" 替换了列表 student_list 中的第一个元素。

    3.5 删除元素

    列表中实现删除元素有两种方式,一种是使用列表的 remove() 方法,另一种是使用列表的 pop() 方法。

    1)remove() 方法

    remove() 方法从左到右查找列表中的元素,如果找到匹配元素则删除,注意如果找到多个匹配元素,只是删除第一个,如果没有找到则会抛出错误。

    remove() 方法语法:list.remove(x)

    其中,参数 x 是要找的元素值。

    使用 remove() 方法删除元素,示例代码如下:

    1. >>> student_list = ['张三', '李四', '王五', '王五']
    2. >>> student_list.remove('王五')
    3. >>> student_list
    4. ['张三', '李四', '王五']
    5. >>> student_list.remove('王五')
    6. >>> student_list
    7. ['张三', '李四']

    2)pop() 方法

    pop() 方法也会删除列表中的元素,但它会将成功删除的元素返回。

    pop() 方法语法:item = list.pop([i])

    其中,参数 i 是指定删除元素的索引,i 可以省略,表示删除最后一个元素。返回值 item 是删除的元素。

    在 Python Shell 中,使用 pop() 方法删除元素示例代码如下:

    1. >>> student_list = ['张三', '李四', '王五']
    2. >>> student_list.pop()
    3. '王五'
    4. >>> student_list
    5. ['张三', '李四']
    6. >>> student_list.pop(0)
    7. '张三'
    8. >>> student_list
    9. ['李四']

    3.6 其他常用方法

    前面介绍列表追加、插入和删除时,已经介绍了一些方法。事实上列表还有很多方法,本节再介绍几个常用方法。

    • reverse():逆置列表。
    • copy():复制列表。
    • clear():清除列表中的所有元素。
    • index(x[, i[, j]]):返回查找 x 第一次出现的索引,i 是开始查找索引,j 是结束查找索引,该方法继承自序列,元组和字符串也可以使用该方法。
    • count(x):返回 x 出现的次数,该方法继承自序列,元组和字符串也可以使用该方法。
    • sort():用于对列表元素进行排序。

    在 Python Shell 中运行示例代码如下:

    1. >>> a = [21, 32, 43, 45] # --1
    2. >>> a.reverse()
    3. >>> a
    4. [45, 43, 32, 21]
    5. >>> b = a.copy() # --2
    6. >>> b
    7. [45, 43, 32, 21]
    8. >>> a.clear() # --3
    9. >>> a
    10. []
    11. >>> b
    12. [45, 43, 32, 21]
    13. >>> a = [45, 43, 32, 21, 32]
    14. >>> a.count(32) # --4
    15. 2
    16. >>> student_list = ['张三', '李四', '王五']
    17. >>> student_list.index('王五') # --5
    18. 2
    19. >>> student_tuple = ('张三', '李四', '王五')
    20. >>> student_tuple.index('王五') # --6
    21. 2
    22. >>> student_tuple.index('李四', 1, 2)
    23. 1
    24. >>> a_list = [3, 4, -2, -30, 14, 9.3, 3.4]
    25. >>> a_list
    26. [3, 4, -2, -30, 14, 9.3, 3.4]
    27. >>> a_list.sort() # --7
    28. >>> a_list
    29. [-30, -2, 3, 3.4, 4, 9.3, 14]

    (1)上述代码第1处调用了 reverse() 方法将列表 a 逆置。

    (2)代码第2处调用 copy() 方法复制列表 a,并赋值给列表 b。

    (3)代码第3处是清楚列表 a 中的元素。

    (4)代码第4处是返回列表 a 中元素值为 32 的元素个数。

    (5)代码第5处是返回元素 '王五' 在 student_list 列表中的索引位置。

    (6)代码第6处是返回元素 '王五' 在 student_list 元组中的索引位置。也就是说,元组对象也有 index() 方法。

    (7)代码第7处是对列表 a_list 进行排序,默认是升序排序。

    3.7 列表推导式

    Python 中有一种特殊表达式——推导式,它可以将一种数据结构作为输入,经过过滤、计算处理后,最后输出另一种数据结构。

    工具数据结构的不同,可分为列表推导式、集合推导式和字典推导式。本节先介绍列表推导式。

    如果想获得 0~9 中偶数的平方数列,可以通过 for 循环实现。代码如下:list_for.py

    1. # coding=utf-8
    2. # 代码文件: 列表/list_for.py
    3. # 获得 0~9 中偶数的平方数列列表,通过for循环实现
    4. n_list = [] # 初始化一个空列表
    5. for x in range(10):
    6. if x % 2 == 0:
    7. n_list.append(x ** 2)
    8. print(n_list)

    运行结果如下:

    [0, 4, 16, 36, 64]

    0~9 中偶数的平方数列也可以通过列表推导式实现,代码如下:list_derivation.py

    1. # coding=utf-8
    2. # 代码文件: 列表/list_derivation.py
    3. # 获得 0~9 中偶数的平方数列列表,通过列表推导式循环实现
    4. n_list2 = [(x ** 2) for x in range(10) if (x % 2 == 0)] # --1
    5. print(n_list2)

    运行结果如下:

    [0, 4, 16, 36, 64]

    (1)上述代码第1处就是列表推导式,可知输出的结果与 for 循环是一样的。下图2 所示是列表推导式语法结构,其中,in 后面的表达式是“输入序列”;for 前面的表达式是“输出表达式”,它的运算结果会保存到一个新列表中;if 条件语句用来过滤输入序列,符合条件的才传递输出表达式,“条件语句” 是可以省略的,所有元素都传递给输出表达式。

    图2  列表推导式语法结构

    条件语句还可以包含多个条件,例如找出 0~99 可以被 5 整除的偶数数列,实现代码如下:

    1. # 列表推导式后面还可以包含多个if条件语句,这些条件语句是逻辑与的关系
    2. # 例如找出0~99可以被5整除的偶数数列
    3. # n_list3 = [x for x in range(100) if (x % 2 == 0) if (x % 5 == 0)] # --1
    4. n_list3 = [x for x in range(100) if (x % 2 == 0) and (x % 5 == 0)] # --2
    5. print(n_list3)

    运行结果:

    [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]

    (1)代码第1处和代码第2处的处理方式是等效的,在代码第1处中,多个 if 条件语句之间是没有使用逻辑运算符连接的,个人建议使用代码第2处的写法更好一些,更加简明清晰。

    四、集合

            集合(set)是一种可迭代的、无序的、不能包含重复元素的数据结构。下图3 是一个班级的集合,其中包含一些学生,这些学生是无序的,不能通过序号访问,而且不能重复。集合又分为可变集合(set)和不可变集合(frozenset)。

    提示】与序列比较,序列中的元素时有序的,可以重复出现,而集合中的元素时无序的,且不能有重复的元素。序列强调的是有序,集合强调的是不重复。当不考虑顺序,而且没有重复的元素时,序列和集合可以相互替换。

    图3  班级的集合

    4.1 创建可变集合

    可变集合类型是 set,创建可变集合可以使用 set([iterable]) 函数,或者用大括号 { } 将元素括起来,元素之间用逗号分隔。

    在 Python Shell 中运行示例代码如下:

    1. >>> a = {'张三', '李四', '王五'} # --1
    2. >>> a
    3. {'王五', '李四', '张三'}
    4. >>> a = {'张三', '李四', '王五', '王五'} # --2
    5. >>> len(a)
    6. 3
    7. >>> a
    8. {'王五', '李四', '张三'}
    9. >>> set((20, 10, 50, 40, 30)) # --3
    10. {40, 10, 50, 20, 30}
    11. >>> b = {} # --4
    12. >>> type(b)
    13. <class 'dict'>
    14. >>> b = set() # --5
    15. >>> type(b)
    16. <class 'set'>

    (1)上述代码第1处是使用大括号{ } 创建集合,如果元素有重复的会怎样呢?

    (2)代码第2处包含有重复的元素,创建时会剔除多余的重复元素,只保留一份。

    (3)代码第3处是使用 set() 函数创建集合对象,

    (4)如果需要创建一个空的集合对象,不能使用大括号{ } 表示,见代码第4处,b 并不是集合而是字典。

    (5)创建空集合要使用空参数的 set() 函数,见代码第5处。

    提示】要获得集合中元素的个数,可以使用 len() 函数,注意 len() 是函数不是方法,本例中 len(a) 表达式返回集合 a 的元素个数,可以发现对于多余的重复元素并没有计算在内,所以 len(a) 返回的结果是 3,而不是 4。

    4.2 修改可变集合

    可变集合类似于列表,可变集合的内容可以被修改,可以向其中插入和删除元素。修改可变集合有几个常用的方法。

    • add(elem):添加元素,如果元素已经存在,则不能添加,不会抛出错误。
    • remove(elem):删除元素,如果元素不存在,则抛出错误。
    • discard(elem):删除元素,如果元素不存在,不会抛出错误。
    • pop():删除返回集合中任意一个元素,返回值是删除的元素。
    • clear():清除集合。

    在 Python Shell 中运行示例代码如下:

    1. >>> student_set = {'张三', '李四', '王五'}
    2. >>> student_set.add('董六')
    3. >>> student_set
    4. {'王五', '李四', '董六', '张三'}
    5. >>> student_set.remove('李四')
    6. >>> student_set
    7. {'王五', '董六', '张三'}
    8. >>> student_set.remove('李四') # --1
    9. Traceback (most recent call last):
    10. File "<stdin>", line 1, in <module>
    11. KeyError: '李四'
    12. >>> student_set.discard('李四') # --2
    13. >>> student_set
    14. {'王五', '董六', '张三'}
    15. >>> student_set.discard('王五')
    16. >>> student_set
    17. {'董六', '张三'}
    18. >>> student_set.pop()
    19. '董六'
    20. >>> student_set
    21. {'张三'}
    22. >>> student_set.clear()
    23. >>> student_set
    24. set()

    (1)上述代码第1处使用 remove() 方法删除元素时,由于要删除的元素 "李四" 已经不在集合中,所以会抛出错误。

    (2)同样是删除集合中不存在的元素,discard() 方法不会抛出错误,见代码第2处。

    4.3 遍历集合

    集合是无序的,没有索引,不能通过下标访问单个元素。但可以遍历集合,访问集合的每一个元素。

    一般使用 for 循环遍历集合,示例代码如下:traverse_set.py

    1. # coding=utf-8
    2. # 代码文件: 集合/traverse_set.py
    3. # 遍历集合,一般通过for循环实现
    4. student_set = {'张三', '李四', '王五'}
    5. for item in student_set:
    6. print(item)
    7. print('---------------')
    8. for i, item in enumerate(student_set): # --1
    9. print('{0} - {1}'.format(i, item))

    运行结果:

    1. 李四
    2. 王五
    3. 张三
    4. ---------------
    5. 0 - 李四
    6. 1 - 王五
    7. 2 - 张三

    (1)代码第1处 for 循环中使用了 enumerate() 函数,该函数在上文的 2.3节—遍历元组中介绍过了,但需要注意的是,此时遍历 i 不是索引,只是遍历集合的次数。

    4.4 不可变集合

    不可变集合类型是 frozenset,创建不可变集合应使用 frozenset([iterable]) 函数,不能使用大括号 { }。

    在 Python Shell 中运行示例程序如下:

    1. >>> student_set = frozenset({'张三', '李四', '王五'}) # --1
    2. >>> student_set
    3. frozenset({'王五', '李四', '张三'})
    4. >>> type(student_set)
    5. <class 'frozenset'>
    6. >>> student_set.add('董六') # --2
    7. Traceback (most recent call last):
    8. File "<stdin>", line 1, in <module>
    9. AttributeError: 'frozenset' object has no attribute 'add'
    10. >>> a = (21, 32, 43, 45)
    11. >>> seta = frozenset(a) # --3
    12. >>> seta
    13. frozenset({32, 45, 43, 21})

    (1)上述代码第1处是创建不可变集合,frozenset() 函数的参数 {'张三', '李四', '王五'} 是另一个集合对象,可以作为 frozenset() 函数的参数。

    (2)代码第3处使用了一个元组对象 a 作为 frozenset() 的参数。

    (3)由于创建的是不可变集合,集合内容不能被修改,所以试图修改会发生错误,见代码第2处,使用 add() 方法发生了错误。

    4.5 集合推导式

    集合推导式与列表推导式类似,区别只是输出结果是集合。修改 3.7节—列表推导式中的 list_derivation.py 代码,给出集合推导式的代码如下:

    代码清单:set_derivation.py

    1. # coding=utf-8
    2. # 代码文件: 集合/set_derivation.py
    3. # 集合推导式的运用
    4. n_set = {x for x in range(100) if x % 2 == 0 if x % 5 == 0}
    5. print(n_set)

    运行结果:

    {0, 70, 40, 10, 80, 50, 20, 90, 60, 30}

    由于集合是不能有重复元素的,集合推导式输出的结果会过滤掉重复的元素,示例代码如下:

    1. input_list = [2, 3, 2, 4, 5, 6, 6, 6]
    2. n_list = [(x ** 2) for x in input_list] # 列表推导式 --1
    3. print(n_list)
    4. n_set2 = {(x ** 2) for x in input_list} # 集合推导式 --2
    5. print(n_set2)

    输出结果:

    1. [4, 9, 4, 16, 25, 36, 36, 36]
    2. {4, 36, 9, 16, 25}

    (1)上述代码第1处是列表推导式,代码第2处是集合推导式,从输出结果可以看出集合推导式是没有重复的元素出现的。

    五、字典

    字典(dict)是可迭代的、可变的数据结构,通过键来访问元素。字典结构比较复杂,它是由两部分视图构成的,一个是键(key)视图,另一个是值(value)视图。键视图不能包含重复元素,而值集合可以,键和值是成对出现的。

    说明】字典(dict)这种类型的数据结构在有些编程语言中也叫 map(映射)。

    下图4 所示是字典结构的“国家代号”。键是国家代号,值是国家名称。

    图4  字典结构的国家代号

    提示】字典更适合通过键来快速访问值,就像查英文字典一样,键就是要查找的英文单词,而值是英文单词的翻译和解释等内容。有的时候,一个英文单词会对应多个翻译和解释,这也是与字典集合特性像对应的。

    5.1 创建字典

    字典类型是 dict,创建字典可以使用 dict() 函数,或者用大括号 { } 将 “键 : 值” 对括起来,“键 : 值” 对之间用冒号分隔。

    在 Python Shell 中运行示例代码如下:

    1. >>> dict1 = {102: '张三', 105: '李四', 109: '王五'} # --1
    2. >>> len(dict1)
    3. 3
    4. >>> dict1
    5. {102: '张三', 105: '李四', 109: '王五'}
    6. >>> type(dict1)
    7. <class 'dict'>
    8. >>>
    9. >>> dict1 = {}
    10. >>> dict1
    11. {}
    12. >>> dict({102: '张三', 105: '李四', 109: '王五'}) # --2
    13. {102: '张三', 105: '李四', 109: '王五'}
    14. >>> dict(((102, '张三'), (105, '李四'), (109, '王五'))) # --3
    15. {102: '张三', 105: '李四', 109: '王五'}
    16. >>> dict([(102, '张三'), (105, '李四'), (109, '王五')]) # --4
    17. {102: '张三', 105: '李四', 109: '王五'}
    18. >>> t1 = (102, '张三')
    19. >>> t2 = (105, '李四')
    20. >>> t3 = (109, '王五')
    21. >>> t = (t1, t2, t3)
    22. >>> dict(t) # --5
    23. {102: '张三', 105: '李四', 109: '王五'}
    24. >>> list1 = [t1, t2, t3]
    25. >>> dict(list1) # --6
    26. {102: '张三', 105: '李四', 109: '王五'}
    27. >>> dict(zip([102, 105, 109], ['张三', '李四', '王五'])) # --7
    28. {102: '张三', 105: '李四', 109: '王五'}

    (1)上述代码第1处是使用大括号 "键 : 值" 对创建字典,这是最简单的创建字典方式。创建一个空字典的表达式是 { }。获得字典长度(即键值对个数)也是使用 len() 函数。

    (2)代码第2处、第3处、第4处、第5处都是使用 dict() 函数创建字典。代码第2处 dict() 函数参数是另一个字典 {102: '张三', 105: '李四', 109: '王五'},使用这种方式创建字典不如直接使用大括号 "键 : 值" 对方式。

    (3)代码第3处和第5处的 dict() 函数参数是一个元组,这个元组中要包含三个只有两个元素的元组,创建过程参考下图5所示。

    图5  使用元组对象创建字典过程

    (4)代码第4处和第6处 dict() 函数参数是一个列表,这个列表中包含三个只有两个元素的元组。

    (5)代码第7处使用了 zip() 函数,zip() 函数将两个可迭代列表对象打包成元组,在创建字典时,可迭代对象元组需要两个可迭代对象,第一个对象是键 ([102, 105, 109]),第二个是值 (['张三', '李四', '王五']),它们包含的元素个数相同,并且一 一对应。

    【注意】使用 dict() 函数创建字典时还可以使用一种 key == value 形式参数,语法如下:

    dict(key1=value1, key2=value2, key3=value3 ...)

    key = value 形式只能创建键是字符串类型的字典,使用时需要省略包裹字符串的引号(包括双引号或单引号)。

    在 Python Shell 中运行示例代码如下:

    1. >>> dict(102 = '张三', 105 = '李四', 109 = '王五') # --1
    2. File "<stdin>", line 1
    3. dict(102 = '张三', 105 = '李四', 109 = '王五')
    4. ^^^^^
    5. SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
    6. >>> dict('102' = '张三', '105' = '李四', '109' = '王五') # --2
    7. File "<stdin>", line 1
    8. dict('102' = '张三', '105' = '李四', '109' = '王五')
    9. ^^^^^^^
    10. SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
    11. >>> dict(S102 = '张三', S105 = '李四', S109 = '王五') # --3
    12. {'S102': '张三', 'S105': '李四', 'S109': '王五'}

    (1)代码第1处试图通过上述 dict() 函数创建键是整数类型的字典,结果发生了错误。

    (2)代码第2处试图使用字符串作为键创建字典,但是该 dict() 函数需要省略字符串的引号,因此会发生错误。

    需要注意的是,本例中键是由数字构成的字符串,它们很特殊,如果省略包裹它们的引号,那么它们会被认为是数字,使用该 dict() 函数是不允许的。

    (3)代码第3处的键在数字前面加了字母 S,这样会被识别为字符串类型,因此能正确创建字典对象。

    5.2 修改字典

    字典可以被修改,但都是针对键和值同时修改,修改字典操作包括添加、替换和删除键值对。

    在Python Shell 中运行示例代码如下:

    1. >>> dict1 = {102: '张三', 105: '李四', 109: '王五'}
    2. >>> dict1[109] # --1
    3. '王五'
    4. >>> dict1[110] = '董六' # --2
    5. >>> dict1
    6. {102: '张三', 105: '李四', 109: '王五', 110: '董六'}
    7. >>> dict1[109] = '张三' # --3
    8. >>> dict1
    9. {102: '张三', 105: '李四', 109: '张三', 110: '董六'}
    10. >>> del dict1[109] # --4
    11. >>> dict1
    12. {102: '张三', 105: '李四', 110: '董六'}
    13. >>> dict1.pop(105)
    14. '李四'
    15. >>> dict1
    16. {102: '张三', 110: '董六'}
    17. >>> del dict1[105]
    18. Traceback (most recent call last):
    19. File "<stdin>", line 1, in <module>
    20. KeyError: 105
    21. >>> dict1.pop(105, '董六') # --5
    22. '董六'
    23. >>> dict1.popitem() # --6
    24. (110, '董六')
    25. >>> dict1
    26. {102: '张三'}

    (1)访问字典中元素可通过下标实现,下标参数是键,返回对应的值,代码第1处中 dict1[109] 是取出字典 dict1 中键为 109 的值:'王五'。

    (2)字典下标访问的元素也可以在赋值符号 “=” 左边,代码第2处是给字典 110 键赋值,注意此时字典对象 dict1 中并没有 110 键,那么这样的操作会添加 110: '董六' 键值对这一元素。

    (3)如果键已存在那么会替换对应的值,如代码第3处会将键 109 对应的值替换为 '张三',虽然此时值视图中已经有 '张三' 了,但仍然可以添加,这说明字典中的值是可以重复的,这里需要再次说明以下,键是不可以重复的。

    (4)代码第4处是删除 109 键对应的值,注意 del 是语句不是函数。使用 del 语句删除键值对时,如果键不存在会抛出错误。

    (5)还可以使用字典中的 pop(key[, default])popitem() 方法删除键值对元素。使用 pop() 方法删除键值对时,如果键不存在则返回默认值(即方法参数 default),见代码第5处。键 105 不存在,所以 dict.pop(105, '董六') 返回了设置的默认值 '董六'。

    (6)popitem() 方法可以删除任意键值对,返回删除的键值对构成元组,上述代码第6处删除了一个键值对,返回一个元组对象 (110, '董六')。

    5.3 访问字典

    字典还需要一些方法用来访问它的键或值,这些方法如下:

    1. get(key[, default]) # 通过键返回值,如果键不存在则返回默认值default
    2. items() # 返回字典的所有键值对
    3. keys() # 返回字典键视图
    4. values() # 返回字典值视图

    在 Python Shell 中运行示例代码如下:

    1. >>> dict1 = {102: '张三', 105: '李四', 109: '王五'}
    2. >>> dict1.get(105) # --1
    3. '李四'
    4. >>> dict1.get(101) # --2
    5. >>> dict1.get(101, '董六') # --3
    6. '董六'
    7. >>> dict1.items() # --4
    8. dict_items([(102, '张三'), (105, '李四'), (109, '王五')])
    9. >>> dict1.keys() # --5
    10. dict_keys([102, 105, 109])
    11. >>> dict1.values() # --6
    12. dict_values(['张三', '李四', '王五'])

    (1)上述代码第1处通过 get() 方法返回 105 键对应的值,如果没有键对应的值,而且还没有为 get() 方法提供默认值,则不会有返回值,见代码第2处。代码第3处提供了返回值。

    (2)代码第4处通过 items() 方法返回字典对象 dict1 所有的键值对。

    (3)代码第5处通过 keys() 方法返回字典对象 dict1 所有的键信息,并以列表的形式出现;代码第6处通过 values() 方法返回字典对象 dict1 所有的值信息,也是以列表的形式出现。

    在访问字典时,也可以使用 in 和 not in 运算符,但需要注意的是,in 和 not in 运算符一般只是在测试键视图中进行。

    在 Python Shell 中运行示例代码如下:

    1. >>> student_dict = {102: '张三', 105: '李四', 109: '王五'}
    2. >>> 102 in dict1
    3. True
    4. >>> '李四' in dict1
    5. False
    6. >>> '李四' not in dict1
    7. True
    8. >>> 101 not in dict1

    5.4 遍历字典

    字典遍历也是字典的主要操作。与集合不同,字典有两个视图,因此遍历过程可以只遍历值视图,也可以只遍历键视图,也可以键值同时遍历。这些遍历过程都是通过 for 循环实现的。

    示例代码如下:traverse_dict.py

    1. # coding=utf-8
    2. # 代码文件: 字典/traverse_dict.py
    3. # 遍历字典,通过for循环实现
    4. student_dict = {102: '张三', 105: '李四', 109: '王五'}
    5. print('--- 遍历键 ---')
    6. for student_id in student_dict.keys(): # --1
    7. print('学号: ' + str(student_id))
    8. print('--- 遍历值 ---')
    9. for student_name in student_dict.values(): # --2
    10. print('学生: ' + student_name)
    11. print('--- 遍历键:值 ---')
    12. for student_id, student_name in student_dict.items(): # --3
    13. print('学号: {0} -> 学生: {1}'.format(student_id, student_name))

    运行结果如下:

    1. --- 遍历键 ---
    2. 学号: 102
    3. 学号: 105
    4. 学号: 109
    5. --- 遍历值 ---
    6. 学生: 张三
    7. 学生: 李四
    8. 学生: 王五
    9. --- 遍历键:值 ---
    10. 学号: 102 -> 学生: 张三
    11. 学号: 105 -> 学生: 李四
    12. 学号: 109 -> 学生: 王五

    (1)上述代码第3处遍历字典的键值对,items() 方法返回键值对元组序列,student_id,student_name 是从元组拆包出来的两个变量。

    5.5 字典推导式

    因为字典包含了键和值两个不同的结构,因此字典推导式结果可以非常灵活。

    字典推导式示例代码如下:dict_derivation.py

    1. # coding=utf-8
    2. # 代码文件: 字典/dict_derivation.py
    3. # 字典推导式的运用
    4. input_dict = {'one': 1, 'two': 2, 'three': 3, 'four': 4}
    5. output_dict = {k: v for k, v in input_dict.items() if v % 2 == 0} # --1
    6. print(output_dict)
    7. keys = [k for k, v in input_dict.items() if v % 2 == 0] # --2
    8. print(keys)

    运行结果:

    1. {'two': 2, 'four': 4}
    2. ['two', 'four']

    (1)上述代码第1处是字典推导式,注意输入结构不是直接使用字典,因为字典不是序列,可以通过字典的 items() 方法返回字典中键值对序列。

    (2)代码第2处也是字典推导式,但只返回键结构。

    5.6 字典的其他常用方法

    我们使用 dir() 函数来查看 dict 类类型包含哪些方法。在 Python Shell 中输入命令:dir(dict)

    1. >>> dir(dict)
    2. ['__class__', '__class_getitem__', '__contains__', '__delattr__', '__delitem__', '__dir__',
    3. '__doc__', '__eq__', '__format__', '__ge__',
    4. '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__',
    5. '__ior__', '__iter__', '__le__', '__len__',
    6. '__lt__', '__ne__', '__new__', '__or__', '__reduce__', '__reduce_ex__', '__repr__',
    7. '__reversed__', '__ror__', '__setattr__', '__setitem__',
    8. '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items',
    9. 'keys', 'pop', 'popitem', 'setdefault', 'update',
    10. 'values']

    1、clear()方法

    clear() 方法用来清空字典中所有的 key-value 对,对一个字典执行 clear() 方法后,该字典就会变成一个空字典。

    示例代码如下:

    1. >>> cars = {'BMW': 8.5, 'Benz': 8.3, 'Audi': 7.9}
    2. >>> cars
    3. {'BMW': 8.5, 'Benz': 8.3, 'Audi': 7.9}
    4. >>> cars.clear()
    5. >>> cars
    6. {}

    2、update() 方法

            update() 方法可使用一个字典所包含的 key-value 对来更新已有的字典。在执行 update() 方法时,如果被更新的字典中已包含对应的 key-value 对,那么原 value 会被覆盖;如果被更新的字典中不包含对应的 key-value 对,则该 key-value 对会被添加进去。

    示例代码如下:

    1. >>> cars = {'BMW': 8.5, 'Benz': 8.3, 'Audi': 7.9}
    2. >>> cars.update({'BMW': 4.5, 'Porsche': 9.3})
    3. >>> cars
    4. {'BMW': 4.5, 'Benz': 8.3, 'Audi': 7.9, 'Porsche': 9.3}

    (1)从运行结果可以看出,由于被更新的字典对象 cars 中已包含 key 为 'Audi' 的key-value 对,因此更新时该 key-value 对的 value 将被改写;但被更新的字典 cars 中不包含 key 为 'Porsche' 的 key-value 对,那么更新时就会为原字典增加一个 key-value 对。

    3、pop()、popitem() 方法

    • pop() 方法用于获取指定 key 对应的 value,并删除这个 key-value 对。

    示例代码如下:

    1. >>> cars = {'BMW': 8.5, 'Benz': 8.3, 'Audi': 7.9}
    2. >>> cars.pop('Audi') # --1
    3. 7.9
    4. >>> cars
    5. {'BMW': 8.5, 'Benz': 8.3}

    (1)上述代码第1处获取键为 'Audi' 对应的值,并删除该键值对。

    • popitem() 方法用于随机弹出字典中的一个 key-value 对。

    提示】此处的随机弹出其实是假的,正如列表的 pop() 方法总是弹出列表中最后一个元素,实际上字典的 popitem() 方法其实也是弹出字典中最后一个 key-value 对。由于字典存储 key-value 对的顺序是不可知的,因此开发者感觉字典的 popitem() 方法是“随机”弹出的,但实际上字典的 popitem() 方法总是弹出底层存储的最后一个 key-value 对。

    示例代码如下:

    1. >>> cars = {'BMW': 8.5, 'Benz': 8.3, 'Audi': 7.9}
    2. >>> cars
    3. {'BMW': 8.5, 'Benz': 8.3, 'Audi': 7.9}
    4. >>> cars.popitem()
    5. ('Audi', 7.9)
    6. >>> cars
    7. {'BMW': 8.5, 'Benz': 8.3}

    从上面的运行结果可以看出,popitem() 方法弹出的是一个元组,因此程序完全可以通过序列解包的方式用两个变量分别接收 key 和 value。

    示例代码如下:

    1. >>> cars = {'BMW': 8.5, 'Benz': 8.3, 'Audi': 7.9}
    2. >>> k, v = cars.popitem()
    3. >>> print(k, v)
    4. Audi 7.9

    4、setdefault() 方法

            setdefault() 方法也用于根据 key 来获取对应 value 的值。但该方法有一个额外的功能:当程序要获取的 key 在字典中不存在时,该方法会先为这个不存在的 key 设置一个默认的 value,然后再返回该 key 对应的 value。总之,setdefault() 方法总能返回指定 key 对应的 value:如果该 key-value 对存在,则直接返回该 key 对应的 value;如果该 key-value 对不存在,则先为该 key 设置默认的 value,然后再返回该 key 对应的 value。

    示例代码如下:

    1. >>> cars = {'BMW': 8.5, 'Benz': 8.3, 'Audi': 7.9}
    2. >>> cars.setdefault('Porsche', 9.2) # --1
    3. 9.2
    4. >>> cars
    5. {'BMW': 8.5, 'Benz': 8.3, 'Audi': 7.9, 'Porsche': 9.2}
    6. >>> cars.setdefault('BMW', 3.4) # --2
    7. 8.5
    8. >>> cars
    9. {'BMW': 8.5, 'Benz': 8.3, 'Audi': 7.9, 'Porsche': 9.2}

    (1)上述第1处调用 setfault() 方法,设置默认值,该 key 在 dict 中不存在,所以新增该 key-value 对。

    (2)代码第2处调用 setdefault() 方法,设置默认值,但该 key 在 dict 中已存在,不会修改 dict 的内容。

    5、fromkeys() 方法

            fromkeys() 方法使用给定的多个 key 创建字典,这些 key 对应的 value 默认都是 None,也可以额外传入一个参数作为默认的 value。该方法一般不会使用字典对象调用(没什么意义),通常会使用 dict 类直接调用。

    例如如下示例代码:

    1. >>> a_dict = dict.fromkeys(['a', 'b']) # --1
    2. >>> a_dict
    3. {'a': None, 'b': None}
    4. >>> b_dict = dict.fromkeys((13, 17)) # --2
    5. >>> b_dict
    6. {13: None, 17: None}
    7. >>> c_dict = dict.fromkeys((13, 17), 'Good') # --3
    8. >>> c_dict
    9. {13: 'Good', 17: 'Good'}

    (1)上述代码第1处,使用类名 dict 直接调用 fromkeys() 方法,列表作为参数,创建包含两个 key 的字典对象 a_dict,value 的默认值为 None。

    (2)代码第2处,使用类名 dict 直接调用 fromkeys() 方法,元组作为参数,创建包含两个 key 的字典对象 b_dict,value 的默认值也是 None。

    (3)代码第3处,使用类名 dict 直接调用 fromkeys() 方法,元组作为第1个参数,并指定了 value 的默认值:"Good",创建包括两个 key 的字典对象 c_dict,value 的值为 "Good"。

    5.7 使用字典格式化字符串

            在格式化字符串时,如果要格式化的字符串模板中包含多个变量,后面就需要按顺序给出多个变量,这种方式对于字符串模板中包含少量变量的情形是合适的,但如果字符串模板中包含大量变量,这种按顺序提供变量的方式则有些不合适。可改为在字符串模板中按 key 指定变量,然后通过字典作为字符串模板中的 key 设置值。

    例如如下程序:dict_format_str.py

    1. # coding=utf-8
    2. # 代码文件: 字典/dict_format_str.py
    3. # 使用字典格式化字符串
    4. # 在字符串模板中使用key
    5. str_template = 'bookName: %(name)s, bookPrice: %(price).2f, bookAuthor: %(author)s'
    6. book1 = {'name': 'Python Programming Language', 'price': 88.9, 'author': 'Jack'}
    7. # 使用字典为字符串模板中的key传入值
    8. print(str_template %book1)
    9. book2 = {'name': 'Java Programming Language', 'price': 78.9, 'author': 'Mike'}
    10. print(str_template %book2)

    运行结果:

    1. >python dict_format_str.py
    2. bookName: Python Programming Language, bookPrice: 88.90, bookAuthor: Jack
    3. bookName: Java Programming Language, bookPrice: 78.90, bookAuthor: Mike

    参考

    《Python从小白到大牛(第1版-2018).pdf》第9章 - 数据结构

    《疯狂Python讲义(2018.12).pdf》第3章 - 列表、元组和字典

  • 相关阅读:
    FL Studio21.2中文高级版数字音乐工作站(DAW)
    C# winfrom窗体最小化任务栏托盘
    CAD圆锥齿轮画法
    ps神经网络滤镜安装包,ps神经网络滤镜用不了
    K8S用户管理体系介绍
    Spring Boot——Thymeleaf
    Java IO Steam
    流水线的实现
    Go 微服务开发框架 DMicro 的设计思路
    [山东科技大学OJ]2415 Problem H: 递归倒排字符串
  • 原文地址:https://blog.csdn.net/u010429831/article/details/125419650