一、字典
字典(dictionary)在其它编程语言中又称作关联数组,也是Python中唯一的映射类型,映射类型对象里哈希值(key)和指向的对象值(value)是一对多的关系。
字典类型是Python中最常用的数据类型之一,它是一个键值对的集合,字典通过键(key)来索引,关联到相对的值(value),理论上它的查询复杂度是O(1),不会随着字典大小的增加而变慢。字典是基于哈希表(HASH TABLES)实现的,采用键值对(key:value)的形式,根据key的值计算value的地址。相比于列表和元组,字典的性能更优,特别是对于查找、添加和删除操作,字典都能在常数时间复杂度内完成。
哈希表(也叫散列表),根据键值对直接进行访问的数据结构。它通过把key和value映射到表中一个位置来访问记录,这种查询速度非常快,更新也快。而这个映射函数叫做哈希函数,存放值的数组叫做哈希表。哈希函数的实现方式决定了哈希表的搜索效率。具体操作过程是:
- 数据添加:把key通过哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。
- 数据查询:再次使用哈希函数将key转换为对应的数组下标,并定位到数组的位置获取value。
但是,对key进行hash的时候,不同的key可能hash出来的结果是一样的,尤其是数据量增多的时候,这个问题叫做哈希冲突。如何解决这种冲突情况呢?
通常的做法有两种,一种是链接法,另一种是开放寻址法,Python选择后者。开放寻址法中,所有的元素都存放在散列表里,当产生哈希冲突时,通过一个探测函数计算出下一个候选位置,如果下一个获选位置还是有冲突,那么不断通过探测函数往下找,直到找个一个空槽来存放待插入元素。
字典是无序的,包含的元素个数不限,值的类型也可以是其它任何数据类型!字典与列表不同之处在于,列表是有序的而字典是无序集合,且不支持索引、元素获取和切片操作。字典的key必须是不可变的对象,例如整数、字符串、bytes和元组都是不可变对象,但使用最多的还是字符串。列表、字典、集合等就不可以作为key。同时,同一个字典内的key必须是唯一的,但值则不必。不可变因为字典根据key来计算value的存储位置,如果每次计算相同的key得出的结果不同,那字典内部就完全混乱了。
Tips:在 Python3.7+,字典被确定为有序(注意:在 3.6 中,字典有序是一个 implementation detail,在 3.7 才正式成为语言特性,因此 3.6 中无法 100% 确保其有序性)
字典可精确描述为不定长、可变、无序、散列的集合类型。字典元素在内存的存储方式是不连续的,也没有链接关系,所以千万不要用列表的序列性质来套字典的无序性质。
字典的每个键值对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中 ,例如:{key1 : value1, key2 : value2 }。
1 2 3 |
>>> name = {1:'andy',2:'eric'} >>> name = {'a':'andy','b':'eric'} >>> name = {'a':[1,2,3],'b':('andy','eric')} |
字典支持的操作
- 索引运算:dict[key]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# 根据Key获取value; >>> name = {'a':[1,2,3],'b':('andy','eric')} >>> name['a'] [1, 2, 3] # 更改key的值; >>> name['a'] = 1 >>> name {'a': 1, 'b': ('andy', 'eric')} # 访问不存在key,抛异常; >>> name['address'] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'address' # 增加一个key-value; >>> name['address'] = 'shanghai' >>> name {'a': 1, 'b': ('andy', 'eric'), 'address': 'shanghai'} |
- 切片运算:字典是不支持切片操作的
1 2 3 4 5 |
>>> name = {'1':'andy','2':'eric'} >>> name['1':'andy'] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type |
但如果字典有字符、列表或元组,这分部是可以做分片的
1 2 3 4 5 |
>>> name = {'a':[1,2,3],'b':('andy','eric')} >>> name['a'] [1, 2, 3] >>> name['a'][1:] [2, 3] |
字典常用内置方法
clear()
清除字典元素。
1 2 3 4 |
>>> name = {'1':'andy','2':'eric'} >>> name.clear() >>> name {} |
copy()
复制字典元素,属于深度复制。
1 2 3 4 5 6 7 8 |
>>> name = {'1':'andy','2':'eric'} >>> namecopy = name.copy() >>> namecopy {'1': 'andy', '2': 'eric'} >>> id(name) 27792064 >>> id(namecopy) 27762416 |
get()
根据键取值,如果键不存在也不会抛异常, 并且可以返回一个给定的默认值。
1 2 3 4 5 |
>>> name = {'1':'andy','2':'eric'} >>> name.get('1') 'andy' >>> name.get('3', 'null') 'null' |
items()
将字典转换为元组列表。
1 2 3 |
>>> name = {'1':'andy','2':'eric'} >>> name.items() [('1', 'andy'), ('2', 'eric')] |
keys()
返回字典中所有键以列表方式。
1 2 3 |
>>> name = {'1':'andy','2':'eric'} >>> name.keys() ['1', '2'] |
values()
返回字典中所有值以列表方式。
1 2 3 4 5 |
>>> name = {'1':'andy','2':'eric'} >>> name.keys() ['1', '2'] >>> name.values() ['andy', 'eric'] |
pop()
根据键,弹出字典中的键值对。
1 2 3 4 5 |
>>> name = {'1':'andy','2':'eric'} >>> name.pop('1') 'andy' >>> name {'2': 'eric'} |
popitem()
随机弹出一对key-value,主要因为字典是无须的。
1 2 3 4 5 |
>>> name = {'1':'andy','2':'eric'} >>> name.popitem() ('1', 'andy') >>> name.popitem() ('2', 'eric') |
update()
可以用来做插入、更新字典(如果有相同的 key 则覆盖原有 key 的值)。
1 2 3 4 5 6 7 8 9 10 11 12 |
>>> name = {'1':'andy','2':'eric'} >>> name.update({'2':'mark'}) >>> name {'1': 'andy', '2': 'mark'} # or >>> name1 = {'1':'andy','2':'eric'} >>> name2 = {'3':'jerry','1':'mark'} >>> name1.update(name2) >>> name1 {'1': 'mark', '3': 'jerry', '2': 'eric'} |
这里需要注意一下,字典更新与合并的区别,字典更新是直接改变了原有对象;而字典合并会产生一个新的对象。在代码中使用的时候一定要知道自己是需要更新字典对象还是生成新的字典对象,不然会造成数据异常。
字典合并常用的方式有下面两种:
1 2 3 4 5 |
# 方法1 new_dict = dict(name1.items() + name2.items()) # 方法2 new_dict = dict(name1, **name2) |
方法 2 其实就是使用深度复制,过程如下:
1 2 |
new_dict = name1.copy() new_dict.update(name2) |
推荐使用方法 2,会比方法 1 速度快很多, 可以用 IPython 测试效率。
iteritems()
迭代器对象抛出一个键值对,直到迭代器抛出异常。
1 2 3 4 5 6 |
>>> name = {'1':'andy','2':'eric'} >>> key = name.iteritems() >>> key.next() ('1', 'andy') >>> key.next() ('2', 'eric') |
iterkeys()
迭代器对象抛出一个键,直到迭代器抛出异常。
1 2 3 4 5 6 |
>>> name = {'1':'andy','2':'eric'} >>> key = name.iterkeys() >>> key.next() '1' >>> key.next() '2' |
itervalues()
迭代器对象抛出一个值,直到迭代器抛出异常。
1 2 3 4 5 6 |
>>> name = {'1':'andy','2':'eric'} >>> key = name.itervalues() >>> key.next() 'andy' >>> key.next() 'eric' |
viewitems()
用集合的方式显示键值对所拆分的原子。
1 2 3 |
>>> name = {'1':'andy','2':'eric'} >>> name.viewitems() dict_items([('1', 'andy'), ('2', 'eric')]) |
has_key()
检查一个key是否存在。
1 2 3 4 5 |
>>> name = {'1':'andy','2':'eric'} >>> name.has_key('2') True >>> name.has_key('3') False |
字典遍历方法
遍历字典获得的键值对是随机无序的!以下是一些常用的遍历方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
dic = {'Name': 'Jack', 'Age': 7, 'Class': 'First'} # 1) 直接遍历字典获取键,根据键取值 for key in dic: print(key, dic[key]) # 2) 利用items方法获取键值,速度很慢,少用 for key,value in dic.items(): print(key,value) # 3) 利用keys方法获取键 for key in dic.keys(): print(key, dic[key]) # 4) 利用values方法获取值,但无法获取对应的键 for value in dic.values(): print(value) |
字典是Python中最常用的数据类型之一,字典看似简单,实际使用起来博大精深、灵活万变,特别适合存储那些需要一一对应的数据类型。但是它的无序特点,在很多时候又让人非常头疼,因此Python内置了一个collections标准库,在这里有一个OrderedDict有序字典类,可以实现字典的有序控制。
二、集合
集合(set)是一个无序不重复元素的集,基本功能包括关系测试和消除重复元素。set和dict类似,也是一组key的集合,但不存储value,不同之处在于set中不允许有重复的key,如果写入有重复的会自动过滤掉。其本质上也是一个哈希表,所以跟字典一样是无序的、也不支持索引、元素获取和切片操作。集合没有特定语法格式,只能通过工厂函数set()/frozenset()创建;但set支持集合关系测试、以及支持成员关系测试。
要创建一个set,需要提供一个list作为输入集合:
1 2 3 |
>>> s = set([1, 2, 3]) >>> s {1, 2, 3} |
注意,传入的参数[1, 2, 3]是一个list,而显示的{1, 2, 3}只是告诉你这个set内部有1,2,3这3个元素,显示的顺序也不表示set是有序的。集合使用列表包含进去但并不意味着它里面包含一个列表,它里面只是一个可迭代对象,而这个可迭代对象里面有3个元素的而已。
重复元素在set中自动被过滤:
1 2 3 |
>>> l = [1, 2, 2, 4] >>> list(set(l)) [1, 2, 4] |
同样对于len、max、sum、min函数,集合也都是支持的。
集合关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
>>> s = set([1,2,3]) >>> t = set([2,3,4]) # 求s和t的并集; >>> s | t set([1, 2, 3, 4]) # 求s和t的交集; >>> s & t set([2, 3]) # 求s和t的差集; >>> s - t set([1]) # 求s和t的对称差集; >>> s ^ t set([1, 4]) |
集合常用的内置方法
clear()
清除一个集合。
1 2 3 4 |
>>> s = set([1,2,3]) >>> s.clear() >>> s set([]) |
copy()
复制一个集合。
1 2 3 4 |
>>> s = set([1,2,3]) >>> t = s.copy() >>> t set([1, 2, 3]) |
pop()
随机弹出一个元素,主要是因为集合是无序的。
1 2 3 4 5 |
>>> s = set([1,2,3]) >>> s.pop() 1 >>> s set([2, 3]) |
update()
合并两个集合,会进行重复过滤。
1 2 3 4 5 |
>>> s = set([1,2,3]) >>> t = set([2,3,4]) >>> s.update(t) >>> s set([1, 2, 3, 4]) |
add()
在集合中添加一个元素。
1 2 3 4 |
>>> s = set([1,2,3]) >>> s.add(4) >>> s set([1, 2, 3, 4]) |
difference()
返回两个集合中的差集。
1 2 |
>>> s.difference(t) set([1]) |
intersection()
返回两个集合中的交集。
1 2 |
>>> s.intersection(t) set([2, 3]) |
union()
返回两个集合中的并集。
1 2 |
>>> s.union(t) set([1, 2, 3, 4]) |
symmetric_difference()
求对称差集。
1 2 |
>>> s.symmetric_difference(t) set([1, 4]) |
集合数据类型属于Python内置的数据类型,但不被重视。其实,集合是一种非常有用的数据结构,它的去重和集合运算是其它内置类型都不具备的功能,在很多场合有着非常重要的作用。
<参考>