《Python从小白到大牛》第9章 数据结构

《Python从小白到大牛》已经上市!

当你有很多书时,你会考虑买一个书柜,将你的书分门别类摆放进入。使用了书柜不仅仅使房间变得整洁,也便于以后使用书时方便查找。在计算机程序中会有很多数据,这些数据也需要一个容器将他们管理起来,这就是数据结构。常见的数据结构:数组(Array)、集合(Set)、列表(List)、队列(Queue)、链表(Linkedlist)、树(Tree)、堆(Heap)、栈(Stack)和字典(Dictionary)等结构。

Python中数据容器主要有:序列、集合和字典。

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

元组

元组(tuple)是一种序列(sequence)结构,下面先来介绍一些序列。

序列

序列(sequence)是一种可迭代的[1],元素是有序的,可以重复出现的数据结构。序列可以通过索引访问元素。图9-1是一个班级序列,其中有一些学生,这些学生是有序的,顺序是他们被放到序列中的顺序,可以通过序号访问他们。这就像老师给进入班级的人分配学号,第一个报到的是“张三”,老师给他分配的是0,第二个报到的是“李四”,老师给他分配的是1,以此类推,最后一个序号应该是“学生人数-1”。

图9-1 序列

序列包括的结构有:列表(list)、字符串(str)、元组(tuple)、范围(range)、和字节序列(bytes)。序列可进行的操作有:索引、分片、加和乘。

  1. 索引操作

序列中第一个元素的索引是0,其他元素的索引是第一个元素的偏移量。可以有正偏移量,称为正值索引;也可以有负偏移量,称为负值索引。正值索引最后一个元素索引是“序列长度-1”,负值索引最后一个元素索引是“-1”。例如Hello字符串,它的正值索引如图9-2(a)所示,它的负值索引如图9-2(b)所示。

图9‑2 索引

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

>>> a = 'Hello'
>>> a[0]
'H'
>>> a[1]
'e'
>>> a[4]
'o'
>>> a[-1]
'o'
>>> a[-2]
'l'
>>> a[5] 
Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    a[5]
IndexError: string index out of range
>>> max(a)
'o'
>>> min(a)
'H'
>>> len(a)
5

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

  1. 序列的加和乘

下面看看序列的加和乘,在前面第7章介绍+和*运算符时,提到过他们可以应用于序列。+运算符可以将两个序列连接起来,*运算符可以将重复多次。

在Python Shell中运行示例:

>>> a = 'Hello'
>>> a * 3
'HelloHelloHello'
>>> print(a)
Hello
>>> a += ' '
>>> a += 'World'
>>> print(a)
Hello World
  1. 序列分片

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

  • [start:end]。start是开始索引,end是结束索引。

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

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

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

>>> a[1:3]
'el'
>>> a[:3]   
'Hel'
>>> a[0:3]
'Hel'
>>> a[0:]   
'Hello'
>>> a[0:5]
'Hello'
>>> a[:]    
'Hello'
>>> a[1:-1] 
'ell'

上述代码表达式a[1:3]是切出1~3之间的子字符串,注意不包括3,所以结果是el。表达式a[:3]省略了开始索引,默认开始索引是0,所以a[:3]与a[0:3]分片结果是一样的。表达式a[0:]省略了结束索引,默认结束索引是序列的长度,即5。所以a[0:]
与a[0:5]分片结果是一样的。表达式a[:]是省略了开始索引和结束索引,a[:]与a[0:5]结果一样。

另外,表达式a[1:-1]使用了负值索引,对照图9-1所示,不难计算出a[1:-1]结果是ell。

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

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

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

>>> a[1:5]
'ello'
>>> a[1:5:2]
'el'
>>> a[0:3]
'Hel'
>>> a[0:3:2]
'Hl'
>>> a[0:3:3]
'H'
>>> a[::-1]
'olleH'

表达式a[1:5]省略了步长参数,步长默认值是1。表达式a[1:5:2]是步长为2,结果是el。a[0:3]分片后的字符串是Hel。而a[0:3:3]是步长为3,分片结果H字符了。当步长为负数时比较麻烦,负数时是从右往左获取元素,所以表达式a[::-1]分片的结果是原始字符串的倒置。

创建元组

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

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

>>> 21,32,43,45             ①
(21, 32, 43, 45)
>>> (21, 32, 43, 45)            ②
(21, 32, 43, 45)
>>> a = (21,32,43,45)
>>> print(a)
(21, 32, 43, 45)
>>> ('Hello', 'World')      ③
 ('Hello', 'World')
>>> ('Hello', 'World', 1,2,3)④
('Hello', 'World', 1, 2, 3)
>>> tuple([21,32,43,45])      ⑤
(21, 32, 43, 45)

代码第①行创建了一个有4个元素的元组,创建元组时使用小括号把元素包裹起来不是必须的。代码第②行使用括号将元素包裹起来,这只是为了提高程序的可读性。Python中没有强制声明数据类型,因此元组中的元素可以是任何数据类型,代码第③行创建是一个字符串元组,代码第④行是创建字符串和整数混合的元组。

另外,元组还有通过tuple([iterable])函数创建,参数iterable是任何可迭代对象。代码第⑤行是使用tuple()函数创建元组对象,实参[21,32,43,45]是一个列表,列表是可迭代对象,可以作为tuple()函数参数创建元组对象。

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

>>> a = (21)
>>> type(a)
<class 'int'>
>>> a = (21,)
>>> type(a)
<class 'tuple'>
>>> a = ()  
>>> type(a)
<class 'tuple'>

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

访问元组

元组做为序列可以通过下标索引访问元素,也可以对其进行分片。在Python
Shell中运行示例代码如下:

>>> a =  ('Hello', 'World', 1,2,3)  ①
>>> a[1]
'World'
>>> a[1:3]
('World', 1)
>>> a[2:]
(1, 2, 3)
>>> a[:2]
('Hello', 'World')

上述代码第①行是元组a,a[1]是访问元组第二个元素,表达式a[1:3]、a[2:]和a[:2]都是进行分片操作。

元组还可以进行拆包(Unpack)操作,就是将元组的元素取出赋值给不同变量。在Python Shell中运行示例代码如下:

>>> a =  ('Hello', 'World', 1,2,3)
>>> str1, str2, n1,n2, n3 = a   ①
>>> str1
'Hello'
>>> str2
'World'
>>> n1
1
>>> n2
2
>>> n3
3
>>> str1, str2, *n = a  ②
>>> str1
'Hello'
>>> str2
'World'
>>> n
[1, 2, 3]
>>> str1,_,n1,n2,_ = a ③

上述代码第①行是将元组a进行拆包操作,接收拆包元素的变量个数应该等于元组个数相同。接收变量个数也可以少于元组个数,代码第②行接收变量个数只有3个,最后一个很特殊,变量n前面有星号,表示将剩下的元素作为一个列表赋值给变量n。另外,还可以使用下划线指定哪些元素不取值,代码第行是不取第二个和第五个元素。

遍历元组

遍历元组一般是使用for循环,示例代码如下:

# coding=utf-8
# 代码文件:chapter9/ch9.1.4.py


a = (21, 32, 43, 45)

for item in a:              ①
    print(item)

print('-----------')
for i, item in enumerate(a):    ②
    print('{0} - {1}'.format(i, item))

输出结果如下:

21
32
43
45
-----------
0 - 21
1 - 32
2 - 43
3 – 45

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

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

列表

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

列表创建

创建列表可以使用list([iterable])函数,或者用中括号[]将元素包裹,元素之间用逗号分隔。在Python Shell中运行示例代码如下:

>>> [20, 10, 50, 40, 30]    ①
[20, 10, 50, 40, 30]
>>> []  
[]
>>> ['Hello', 'World', 1, 2, 3] ②
['Hello', 'World', 1, 2, 3]
>>> a = [10]        ③
>>> type(a)
<class 'list'>
>>> a = [10,]       ④
>>> type(a)
<class 'list'>
>>> list((20, 10, 50, 40, 30))  ⑤
[20, 10, 50, 40, 30]

上述代码第①行创建一个有5个元素的列表,注意中括号不能省略,如果省略了中括号那就变成了元组了。创建空列表是[]表达式。列表中可以放入任何对象,代码第②行是创建一个字符串和整数混合的列表。代码第③行是创建只有一个元素的列表,中括号不能省略。另外,无论是元组还是列表,每一个元素后面都跟着一个逗号,只是最后一个元素的逗号经常是省略的,代码第④行最后一个元素没有省略逗号。

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

追加元素

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

append()方法语法:

list.append(x)

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

extend()方法语法:

list.extend(t)

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

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

>>> student_list = ['张三', '李四', '王五']   
>>> student_list.append('董六')       ①
>>> student_list
['张三', '李四', '王五', '董六']
>>> student_list += ['刘备', '关羽']        ②
>>> student_list
['张三', '李四', '王五', '董六', '刘备', '关羽']
>>> student_list.extend(['张飞', '赵云'])   ③
>>> student_list
['张三', '李四', '王五', '董六', '刘备', '关羽', '张飞', '赵云']

上述代码中第①行使用了append方法,在列表后面追加一个元素,append()方法不能同时追加多个元素。代码第②行是利用+=运算符追加多个元素,能够支持+=运算是因为列表支持+运算。代码第③行是使用extend()方法追加多个元素。

插入元素

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

list.insert(i, x)

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

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

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

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

替换元素

列表具有可变性,其中的元素替换,替换元素很简单,通过列表下标索引元素放在赋值符号(=)左边,进行赋值即可替换。在Python
Shell中运行示例代码如下:

>>> student_list = ['张三', '李四', '王五']
>>> student_list[0] = "诸葛亮"
>>> student_list
['诸葛亮', '李四', '刘备', '王五']

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

删除元素

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

  1. remove()方法

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

remove()方法语法:

list.remove(x)

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

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

>>> student_list = ['张三', '李四', '王五', '王五']
>> student_list.remove('王五')
>>> student_list
['张三', '李四', '王五']
>>> student_list.remove('王五')
>>> student_list
['张三', '李四']
  1. pop()方法

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

item = list.pop([i])

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

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

>>> student_list = ['张三', '李四', '王五']
>>> student_list.pop()
'王五'
>>> student_list
['张三', '李四']
>>> student_list.pop(0)
'张三'
>>> student_list
['李四']

其他常用方法

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

  • reverse()。倒置列表。

  • copy()。复制列表。

  • clear()。清除列表中的所有元素。

  • index(x[, i[,
    j]])。返回查找x第一次出现的索引,i是开始查找索引,j是结束查找索引。该方法继承自序列,元组和字符串也可以使用该方法。

  • count(x)。返回x出现的次数。该方法继承自序列,元组和字符串也可以使用该方法。

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

>>> a = [21, 32, 43, 45]
>>> a.reverse()     ①
>>> a
[45, 43, 32, 21]
>>> b = a.copy()    ②
>>> b
[45, 43, 32, 21]
>>> a.clear()           ③   
>>> a
[]
>>> b
[45, 43, 32, 21]
>>> a = [45, 43, 32, 21, 32]
>>> a.count(32)     ④
2
>>> student_list = ['张三', '李四', '王五']
>>> student_list.index('王五')    ⑤
2
>>> student_tuple = ('张三', '李四', '王五')
>>> student_tuple.index('王五')   ⑥
2
>>> student_tuple.index('李四', 1 , 2)
1

上述代码中第①行是调用reverse()方法将列表a倒置。代码第②行是调用copy()方法复制a,并赋值给b。代码第③行是清除a中元素。代码第④行是返回a列表中32元素的个数。代码第⑤行是返回'王五'在student_list列表中的位置。代码第⑥行是返回'王五'在student_tuple元组中的位置。

列表推导式

Python中有一种特殊表达式——推导式,它可以将一种数据结构作为输入,经过过滤、计算等处理,最后输出另一种数据结构。根据数据结构的不同分为:列表推导式、集合推导式和字典推导式。本节先介绍列表推导式。

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

# coding=utf-8
# 代码文件:chapter9/ch9.2.7.py

n_list = []
for x in range(10):
    if x % 2 == 0:
        n_list.append(x ** 2)
print(n_list)

输出结构如下:

[0, 4, 16, 36, 64]

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

n_list = [x ** 2 for x in range(10) if x % 2 == 0]  ①
print(n_list)

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

图9‑3 索引

条件语句可以包含多个条件,如果想找出0~99之间的偶数,而且可以被5整除数列,实现代码如下:

n_list = [x for x in range(100) if x % 2 == 0 if x % 5 == 0]  
print(n_list)

列表推导式的条件语句有两个if x % 2 == 0和if x % 5 == 0,可见他们“与”的关系。

集合

集合(set)是一种可迭代的、无序的、不能包含重复元素的数据结构。图9-4是一个班级的集合,其中包含一些学生,这些学生是无序的,不能通过序号访问,而且不能有重复的同学。

图9-4 集合

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

集合又分为可变集合(set)和不可变集合(frozenset)。

创建可变集合

可变集合类型是set,创建可变集合可以使用set([iterable])函数,或者用大括号{}将元素包裹,元素之间用逗号分隔。在Python
Shell中运行示例代码如下:

>>> a = {'张三', '李四', '王五'}      ①
>>> a
{'张三', '李四', '王五'}
>>> a = {'张三', '李四', '王五', '王五'}②
>>> len(a)
3
>>> a
{'张三', '李四', '王五'}
>>> set((20, 10, 50, 40, 30)) ③
{40, 10, 50, 20, 30}
>>> b = {}          ④
>>> type(b)
<class 'dict'>
>>> b = set()       ⑤
>>> type(b)
<class 'set'>

上述代码第①行是使用大括号创建集合,如果元素有重复的会怎样呢?代码第②行包含有重复的元素,创建时会剔除重复元素。代码第③行是使用set()函数创建集合对象。如果要创建一个空的集合不能使用{}表示,见代码第④行b并不是集合而是字典,而是使用空参数的set()函数,见代码第⑤行。

提示
要获得集合中元素的个数,可以使用len()函数,注意len()是函数不是方法,本例中len(a)表达式返回集合a的元素个数。

修改可变集合

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

  • add(elem)。添加元素,如果元素已经存在,则不能添加,不会抛出错误。

  • remove(elem)。删除元素,如果元素不存在,则抛出错误。

  • discard(elem)。删除元素,如果元素不存在,不会抛出错误。

  • pop()。删除返回集合中任意一个元素,返回值是删除的元素。

  • clear()。清除集合。

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

>>> student_set = {'张三', '李四', '王五'}
>>> student_set.add('董六')
>>> student_set
{'张三', '董六', '李四', '王五'}
>>> student_set.remove('李四')
>>> student_set
{'张三', '董六', '王五'}
>>> student_set.remove('李四')    ①
Traceback (most recent call last):
  File "<pyshell#144>", line 1, in <module>
    student_set.remove('李四')
KeyError: '李四'
>>> student_set.discard('李四')   ②
>>> student_set
{'张三', '董六', '王五'}
>>> student_set.discard('王五')
>>> student_set
{'张三', '董六'}
>>> student_set.pop()       
'张三'
>>> student_set
{'董六'}
>>> student_set.clear()
>>> student_set
set()

上述代码第①行使用remove()方法删除元素时,由于要删除的'李四'已经不在集合中,所以会抛出错误。而同样是删除集合中不存在的元素discard()方法不会抛出错误,见代码第②行。

遍历集合

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

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

# coding=utf-8
# 代码文件:chapter9/ch9.3.3.py

student_set = {'张三', '李四', '王五'}

for item in student_set:     
    print(item)

print('-----------')
for i, item in enumerate(student_set):    ①
    print('{0} - {1}'.format(i, item))

输出结果如下:

张三
王五
李四
-----------
0 - 张三
1 - 王五
2 - 李四

上述中使用for循环遍历集合,代码第①行的for循环中使用了enumerate()函数,该还是在9.1.4节遍历元组时已经介绍过了,但是需要注意的是,此时变量i不是索引,只是遍历集合的次数。

不可变集合

不可变集合类型是frozenset,创建不可变集合使用frozenset([iterable])函数,不能使用大括号{}。在Python Shell中运行示例代码如下:

>>> student_set = frozenset({'张三', '李四', '王五'})  ①  
>>> student_set
frozenset({'张三', '李四', '王五'})
>>> type(student_set)
<class 'frozenset'>
>>> student_set.add('董六')   ②
Traceback (most recent call last):
  File "<pyshell#168>", line 1, in <module>
    student_set.add('董六')
AttributeError: 'frozenset' object has no attribute 'add'
>>> a = (21, 32, 43, 45)
>>> seta = frozenset(a) ③
>>> seta
frozenset({32, 45, 43, 21})

上述代码第①行是创建不可变集合,frozenset()的参数{'张三', '李四',
'王五'}是另一个集合对象,因为集合也是可迭代对象,可以作为frozenset()的参数。代码第③函数使用的了一个元组a作为frozenset()的参数。

由于创建的是不变集合,不能被修改,所以视图修改发生错误,见代码第②行,使用add()发生错误。

集合推导式

集合推导式与列表推断式类似,区别只是输出结构是集合。修改9.2.7节代码如下:

# coding=utf-8
# 代码文件:chapter9/ch9.3.5.py

n_list = {x for x in range(100) if x % 2 == 0 if x % 5 == 0}
print(n_list)

输出结构如下:

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

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

input_list = [2, 3, 2, 4, 5, 6, 6, 6]

n_list = [x ** 2 for x in input_list] ①
print(n_list)

n_set = {x ** 2 for x in input_list} ②
print(n_set)

输出结构如下:

[4, 9, 4, 16, 25, 36, 36, 36]
{4, 36, 9, 16, 25}

上述代码第①行是列表推导式。代码第②行是集合推导式,从结果可见没有重复的元素。

字典

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

图9-5所示是字典结构的“国家代号”。键是国家代号,值是国家。

图9-5 字典

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

创建字典

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

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

>>> dict1 = {102: '张三', 105: '李四', 109: '王五'} ①
>>> len(dict1)
3
>>> dict1
{102: '张三', 105: '李四', 109: '王五'}
>>> type(dict1)
<class 'dict'>
>>> dict1  = {}
>>> dict1
{}
>>> dict({102: '张三', 105: '李四', 109: '王五'})  ②
{102: '张三', 105: '李四', 109: '王五'}
>>> dict(((102, '张三'), (105, '李四'), (109, '王五'))) ③
{102: '张三', 105: '李四', 109: '王五'}
>>> dict([(102, '张三'), (105, '李四'), (109, '王五')]) ④
{102: '张三', 105: '李四', 109: '王五'}
>>> t1 = (102, '张三')
>>> t2 = (105, '李四')
>>> t3 = (109, '王五')
>>> t = (t1, t2, t3) 
>>> dict(t) ⑤
{102: '张三', 105: '李四', 109: '王五'}
>>> list1 = [t1, t2, t3] 
>>> dict(list1) ⑥
{102: '张三', 105: '李四', 109: '王五'}
>>> dict(zip([102, 105, 109], ['张三', '李四', '王五']))  ⑦
{102: '张三', 105: '李四', 109: '王五'}

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

代码第②行、第③行、第④行、第⑤行和第⑥行都用dict()函数创建字典。代码第②行dict()函数参数是另外一个字典{102:
'张三', 105: '李四', 109: '王五'},使用这种方式不如直接使用大括号“键:值”。

代码第③行和第⑤行参数是一个元组,这个元组中要包含三个只有两个元素的元组,创建过程参考如图9-6所示。代码第④行和第⑥行参数是一个列表,这个列表中包含三个只有两个元素的元组。

图9-6 创建字典

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

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

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

key=value形式只能创建键是字符串类型的字典,使用时需要省略包裹字符串的引号(包括双引号或单引号)。在Python
Shell中运行示例代码如下:

>>> dict(102 = '张三', 105 = '李四', 109 = '王五') ①

SyntaxError: keyword can't be an expression

>>> dict('102' = '张三', '105' = '李四', '109' = '王五') ②

SyntaxError: keyword can't be an expression

>>> dict(S102 = '张三', S105 = '李四', S109 = '王五') ③

{'S102': '张三', 'S105': '李四', 'S109': '王五'}

代码第①行试图通过上述dict()函数创建键是整数类型的字典,结果会发生错误。代码第②行是试图使用字符串作为键创建字典,但是该dict()函数需要省略字符串键的引号,因此会发生错误。需要注意本例中键是由数字构成的字符串,他们很特殊如果省略包裹他们的引号,那么他们会表示为数字,使用该dict()函数是不允许的,所以此时的键不会识别字符串类型。代码第③行的键是在数字前面加S字母,这样不会识别为字符串类型。

修改字典

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

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

>>> dict1 = {102: '张三', 105: '李四', 109: '王五'} 
>>> dict1[109] ①
'王五'
>>> dict1[110] = '董六' ②
>>> dict1
{102: '张三', 105: '李四', 109: '王五', 110: '董六'}
>>> dict1[109] = '张三' ③
>>> dict1
{102: '张三', 105: '李四', 109: '张三', 110: '董六'}
>>> del dict1[109] ④
>>> dict1
{102: '张三', 105: '李四', 110: '董六'}
>>> dict1.pop(105)
'李四'
>>> dict1
{102: '张三', 110: '董六'}
>>> dict1.pop(105, '董六') ⑤
'董六'
>>> dict1.popitem() ⑥
(110, '董六')
>>> dict1
{102: '张三'}

访问字典中元素可通过下标实现,下标参数是键,返回对应的值,代码第①行是dict1[109]是取出字典dict1中键为109的值。字典下标访问元素也可以在赋值符号(=)左边,代码第②行是字典110键赋值,注意此时字典dict1中没有110键,那么这样的操作会添加110:
'董六'键值对。如果键存在那么会替换对应的值,代码第③行会将键109对应的值替换为'张三',虽然此时值视图中已经有'张三'了,但仍然可以添加,这说明值是可以重复的。

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

如果喜欢使用方法删除元素,可以使用字典的pop(key[,
default])和popitem()方法。pop(key[,
default])方法删除键值对,如果键不存在则返回默认值(default),见代码第⑤行是105键不存在返回默认值'董六'。popitem()方法删除任意键值对,返回删除的键值对,构成的元组,上述代码第⑥行删除了一个键值对,返回一个元组对象(110,
'董六')。

访问字典

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

  • get(key[, default])。通过键返回值,如果键不存在返回默认值。

  • items()。返回字典的所有键值对。

  • keys()。返回字典键视图。

  • values()。返回字典值视图。

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

>>> dict1 = {102: '张三', 105: '李四', 109: '王五'}
>>> dict1.get(105)  ①
'李四'
>>> dict1.get(101)  ②
>>> dict1.get(101, '董六') ③
'董六'
>>> dict1.items() 
dict_items([(102, '张三'), (105, '李四'), (109, '王五')])
>>> dict1.keys() 
dict_keys([102, 105, 109])
>>> dict1.values() 
dict_values(['张三', '李四', '王五'])

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

在访问字典时,也可以使用in和not in运算符,但是需要注意的是in和not
in运算符只测试键视图中进行。在Python Shell中运行示例代码如下:

>>> student_dict = {'102': '张三', '105': '李四', '109': '王五'}
>>> 102 in dict1
True
>>> '李四' in dict1
False

遍历字典

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

示例代码如下:

# coding=utf-8
# 代码文件:chapter9/ch9.4.4.py

student_dict = {102: '张三', 105: '李四', 109: '王五'}  

print('---遍历键---')
for student_id in student_dict.keys():  ①
    print('学号:' + str(student_id))

print('---遍历值---')
for student_name in student_dict.values(): ②
    print('学生:' + student_name)

print('---遍历键:值---')
for student_id, student_name in student_dict.items(): ③ 
    print('学号:{0} - 学生:{1}'.format(student_id, student_name))

输出结果如下:

---遍历键---
学号:102
学号:105
学号:109
---遍历值---
学生:张三
学生:李四
学生:王五
---遍历键:值---
学号:102 - 学生:张三
学号:105 - 学生:李四
学号:109 - 学生:王五

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

字典推导式

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

# coding=utf-8
# 代码文件:chapter9/ch9.4.5.py

input_dict = {'one': 1, 'two': 2, 'three': 3, 'four': 4} 

output_dict = {k: v for k, v in input_dict.items() if v % 2 == 0} ①
print(output_dict)

keys = [k for k, v in input_dict.items() if v % 2 == 0]  ②
print(keys)

输出结构如下:

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

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

本章小结

本章介绍了Python中的几种数据结构。其中包括序列、元组、集合和字典,了解序列的特点,清楚序列包括哪些结构。然后详细介绍了元组、集合和字典。

配套视频

http://www.zhijieketang.com/classroom/10/courses

配套源代码

http://www.zhijieketang.com/group/8

纸质版电商

京东:https://item.jd.com/12468732.html
当当:http://product.dangdang.com/25574315.html

作者微博:@tony_关东升
邮箱:eorient@sina.com
智捷课堂微信公共号:zhijieketang
Python读者服务QQ群:565736812


  1. 可迭代(iterable),是指它的成员能返回一次的对象。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,784评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,745评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,702评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,229评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,245评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,376评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,798评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,471评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,655评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,485评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,535评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,235评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,793评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,863评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,096评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,654评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,233评论 2 341

推荐阅读更多精彩内容