1.1.4 字典
dict可能是Python内建数据结构中最重要的,它是拥有灵活键值对集合,其中键和值都是Python对象。用{ }是创建字典的一种方式,在字典中用逗号将键值对分隔
In [1]: empty_dict = {} In [2]: d1 = {'a' : 'some_value', 'b' : '[1, 2, 3, 4]'} In [3]: d1 Out[3]: {'a': 'some_value', 'b': '[1, 2, 3, 4]'}
可以访问、插入或设置字典中的元素
In [4]: d1[7] = 'an integer' In [5]: d1 Out[5]: {'a': 'some_value', 'b': '[1, 2, 3, 4]', 7: 'an integer'}
可以用检查列表或元祖中是否含有一个元素的相同语法来检查字典中是否含有一个键
In [6]: 'b' in d1 Out[6]: True
可以使用del关键字或pop方法来删除值,pop方法在删除的同时返回被删的值,并删除键
In [7]: d1[5] = 'some value' In [8]: d1 Out[8]: {'a': 'some_value', 'b': '[1, 2, 3, 4]', 7: 'an integer', 5: 'some value'} In [9]: d1['dummy'] = 'another value' In [10]: d1 Out[10]: {'a': 'some_value', 'b': '[1, 2, 3, 4]', 7: 'an integer', 5: 'some value', 'dummy': 'another value'} In [11]: del d1[5] In [12]: d1 Out[12]: {'a': 'some_value', 'b': '[1, 2, 3, 4]', 7: 'an integer', 'dummy': 'another value'} In [13]: ret = d1.pop('dummy') In [14]: ret Out[14]: 'another value' In [15]: d1 Out[15]: {'a': 'some_value', 'b': '[1, 2, 3, 4]', 7: 'an integer'}
keys方法和value方法会分别提供字典键、值的迭代器。然而键值对并没有特定的顺序,这些函数输出的键、值都是按照相同的顺序
In [20]: list(d1.keys()) Out[20]: ['a', 'b', 7] In [21]: list(d1.values()) Out[21]: ['some_value', '[1, 2, 3, 4]', 'an integer']
可以使用update方法将两个字典合并
In [22]: d1.update({'b' : 'foo', 'c' : 12}) In [23]: d1 Out[23]: {'a': 'some_value', 'b': 'foo', 7: 'an integer', 'c': 12}
update方法改变了字典中元素位置,因此对于任何原字典中已经存在的键,如果传给update方法的数据也含有相同的键,则它的值会被覆盖。
1.1.4.1 从序列生成字典
由于字典本质上是2-元祖(包含两个元素的元祖)的集合,字典是可以接受一个2-元祖的列表作为参数的
In [24]: mapping = dict(zip(range(5), reversed(range(5)))) In [25]: mapping Out[25]: {0: 4, 1: 3, 2: 2, 3: 1, 4: 0}
1.1.4.2 默认值
通常情况下会有这样的代码逻辑
if key in some_dict: value = some_dict[key] else: value = default_value
不过字典中的get方法和pop方法可以返回一个默认值,因此上述的if-else代码块可以简写成:
value = some_dict.get(key, default_value)
带默认值的get方法会在参数不是字典的键时返回None,而pop会抛出异常。
一个常见的场景是字典中的值集合通过设置,成为另一种集合,比如列表。
比如将字词组成的列表根据首字母分类位包含列表的字典
In [29]: words = ['apple', 'bat', 'bar', 'atom', 'book'] In [30]: by_letter = {} In [31]: for word in words: ...: letter = word[0] ...: if letter not in by_letter: ...: by_letter[letter] = [word] ...: else: ...: by_letter[letter].append(word) In [32]: by_letter Out[32]: {'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']}
字典的setdefault方法就是为了这个目的而产生的。上述for循环语句可以被写为
In [34]: for word in words: ...: letter = word[0] ...: by_letter.setdefault(letter, []).append(word) ...: In [35]: by_letter Out[35]: {'a': ['apple', 'atom', 'apple', 'atom'], 'b': ['bat', 'bar', 'book', 'bat', 'bar', 'book']}
内建的集合模块有一个非常有用的类,defaultdict。这个类是的上述目的实现更为简单。想要生成符合要求的字典,可以向字典中传入类型或能在各个位置生成默认值的函数
from collections import defaultdict by_letter = defaultdict(list) for word in words: by_letter[word[0]].append(word)
1.1.4.3 有效的字典键类型
尽管字典的值可以是Python任何对象,但是键必须是不可变对象,比如标量类型(整数、浮点数、字符串)或元组(且元祖内对象也必须是不可变对象)。
这里要使用到一个术语叫做“哈希化”,通过hash函数可以检查一个对象是否可以哈希化(即是否可以用作字典的键)
In [38]: hash('string') Out[38]: -2637463646601815264 In [39]: hash((1, 2, (3, 4))) Out[39]: -2725224101759650258 In [40]: hash((1, 2, [2, 3])) # 会因为列表是可变的失败 --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-40-8ffc25aff872> in <module> ----> 1 hash((1, 2, [2, 3])) TypeError: unhashable type: 'list
为了将列表作为键,一种方式就是将其转换为元组,而元祖只要它内部的元素都可以哈希化,则它自己也可以哈希化
In [41]: d = {} In [42]: d[tuple([1, 2, 3])] = 5 In [43]: d Out[43]: {(1, 2, 3): 5}
1.1.5 集合
集合是一种无序且元素唯一的容器。可以认为集合也像字典,但是只有键没有值。集合可以有两种创建方式:通过set函数或者是用字面值集与大括号的语法
In [44]: set([2, 2, 2, 1, 3, 3]) Out[44]: {1, 2, 3} # 元素唯一 In [45]: {2, 2, 2, 1, 3, 3} Out[45]: {1, 2, 3}
集合支持数学上的集合操作,例如联合、交集、差集、对称差集。
考虑一下示例集合
In [46]: a = {1, 2, 3, 4, 5}
In [47]: b = {3, 4, 5, 6, 7, 8}
两个集合的联合就是两个集合中不同元素的并集。可以通过union方法或 | 二元操作符完成
In [48]: a.union(b) Out[48]: {1, 2, 3, 4, 5, 6, 7, 8} In [49]: a|b Out[49]: {1, 2, 3, 4, 5, 6, 7, 8}
交集包含了两个集合中同时包含的元素。可以使用&操作符或intersection方法获得交集。
In [50]: a.intersection(b) Out[50]: {3, 4, 5} In [51]: a & b Out[51]: {3, 4, 5}
常用的集合方法列表
函数 | 替代方法 | 描述 |
a.add(x) | 将元素x添加到a | |
a.clear() | 将集合重置为空,清空所有元素 | |
a.remove(x) | 从集合a中移除某个元素 | |
a.pop() | 移除任意元素如果集合时空的抛出key error | |
a.union(b) | a|b | a和b中所有的不同元素 |
a.update(b) | a|=b | 将a的内容设置为a和b的并集 |
a.intersection(b) | a&b | a和b中同时包含的元素 |
a.intersection_update(b) | a&=b | 将a的内容设置为a和b的交集 |
a.difference(b) | a-b | 在a不在b的元素 |
a.difference_update(b) | a-=b | 将a的内容设为在a不在b的元素 |
a.symmetric_difference(b) | a^b | 所有在a或b中但不是同时在a和b中 |
a.symmetric_difference_update(b) | a^=b | 将a的内容设置为所有在a或b中但不是同时在a和b中的元素 |
a.issubset(b) | 如果a包含于b则返回True | |
a.issuperset(b) | 如果a包含b返回True | |
a.isdisjoint(b) | a,b没有交集返回True |
当且仅当两个集合的内容一模一样的时候,两个集合才相等
In [52]: {1, 2} == {1, 2}
Out[52]: True
1.1.6列表、集合和字典的推导式
列表推导式是最受欢迎的Python语言特性之一。它允许你过滤一个容器的元素,用一种简明的表达式传递给过滤器的元素,从而生成一个新的列表。列表推导式的基本表达式为
[expr for val in collection if condition]
这与下面的for循环的是等价的
result = [] for val in collection: if condition: result.append(expr)
过滤条件是可以忽略的。只保留表达式。例如,给定一个字符串列表,我们可以过滤长度大于2的,并将字母改为大写
In [53]: string = ['a', 'as', 'bat', 'car', 'above', 'python'] In [54]: [x.upper() for x in string if len(x) > 2] Out[54]: ['BAT', 'CAR', 'ABOVE', 'PYTHON']
集合与字典的推导式是列表推导式的自然拓展,用相似的方式生成集合与字典。字典推导式如下:
dict_comp = {key-expr : value-expr for value in collection if condition}
集合推导式看起来很像列表推导式,只是中括号变成了大括号
set_comp = {expr for value in collection if condition}
和列表推导式类似,集合、字典的推导式非常方便,它们使代码更易读易写。如果有一个字符串的列表,假设我们想要一个集合,集合包含列表中字符串的长度,我可以通过集合推导式很方便的实现
In [56]: unique_length = {len(x) for x in string} In [57]: unique_length Out[57]: {1, 2, 3, 4, 6}
也可以使用map函数更函数化、更简便地表达
In [58]: set(map(len, string))
Out[58]: {1, 2, 3, 4, 6}
创建一个将字符串与其位置相匹配的字典作为字典推导式的简单示例
# enumerate 返回(i, value)元组的序列,其中value是元素的值,i是元素的索引 In [59]: loc_mapping = {val : index for index, val in enumerate(string)} In [60]: loc_mapping Out[60]: {'python': 0, 'a': 1, 'dove': 2, 'car': 3, 'bat': 4, 'as': 5}
1.1.6.1 嵌套列表推导式
假设有一个包含列表的列表,内容是英语姓名和西班牙语的姓名
In [64]: all_data = [['John', 'Emily', 'Michael', 'Mary', 'Steven'], ['Maria', 'Juan', 'Javier', 'Natalia', 'Pilar']]
假设我们想要获得一个列表包含所有含有两个以上字母e的名字,可以简单使用for循环
names_of_interest = [] for names in all_data: enough_es = [name for name in names if name.count('e') >= 2] names_of_interest.extend(enough_es)
实际上可以使用一个嵌套列表推导式来完成
In [65]: result = [name for names in all_data for name in names if name.count('e') >= 2] In [66]: result Out[66]: ['Steven']
列表推导式的for循环部分是根据嵌套的顺序排列的,所有的过滤条件像之前一样被放在尾部。下面的例子是将含有整数元祖的列表扁平化为一个简单的整数列表
In [69]: some_tuples = [(1, 2, 3), (4, 5, 6), (7, 8, 9)] In [70]: flattened = [x for tup in some_tuples for x in tup] In [71]: flattened Out[71]: [1, 2, 3, 4, 5, 6, 7, 8, 9]
for表达式的顺序应当和所写的嵌套for循环的顺序一致
flattened = [] for tup in some_tuples: for x in tup: flattened.append(x)
嵌套推导式的语法要和列表推导式中的列表推导式区分开,列表推导式中的列表推导式也是非常有效的
In [72]: [[x for x in tup] for tup in some_tuples] Out[72]: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
上面的代码创建了一个包含列表的列表,而不是内部所有元素扁平排列。