第25~26讲:字典:当索引不好用时
一 字典https://www.cnblogs.com/linupython/p/5880280.html
1 概念/特点
- 字典是另一种可变容器模型,且可存储任意类型对象。
- 字典是python中唯一的映射类型,采用键值对(key-value)的形式存储数据。python对key进行哈希函数运算,根据计算的结果决定value的存储地址,所以字典是无序存储的,且key必须是可哈希的,可哈希表示键值不可变。
总结:
- 字典值可以没有限制地取任何python对象,既可以是标准的对象,也可以是用户定义的,但键不行。
- 不允许同一个键出现两次。创建时如果同一个键被赋值两次,后一个值会被记住
- 键必须不可变,所以可以用数字,字符串或元组充当,所以用列表就不行
2 定义:字典可通过一对花括号来定义,元素内部通过冒号来连接key和对应的value,元素与元素之间通过逗号隔开,可进行自由嵌套。
3 语法格式:d = {key1 : value1, key2 : value2 }
4 字典的相关操作
- 创建
- 通过定义字典的过程创建 。 dict = {'a':1,'b':2,'c':3}
- dict()函数:也可以创建字典,工厂函数/类型函数
- 注意:在使用dict函数之前先把原来定义的dict字典给删了,用'del dict'命令,否则系统会报错:TypeError: 'dict' object is not callable
- 通过元组定义:dict1 = dict((('F',70),('i',105),('s',115),('h',104),('C',67)))
- 通过关键字定义:dict1 = dict(a = 97,b=98)
- 访问:通过在方括号内引用其键名来访问字典的元素。 print(dict['c'])
- 修改:
- 更改已有键值对的值:通过引用其键名来更改特定项的值。dict['a'] = 0
- 增加新的键值对:通过引用要添加的'key'并给该'key'赋值来添加新的键值对: dict['d']=4
- 删除:字典长度:获取字典的长度(键值对的个数),通过len方法实现。 len(dict)
- del语句:
- 删除整个字典。del dict
- 删除字典中的某个键值对。del dict['a']
- clear()方法:清空字典所有键值对,不需要参数。dict.clear()
- pop()方法:删除字典的某一键值对,其参数直接是键值key,不需要通过字典名称引用。dict.pop('c')
- popitem() 方法:删除最后插入的项目(在 3.7 之前的版本中,删除随机项目)。 dict.popitem()
- del语句:
- 字典长度:获取字典的长度(键值对的个数),通过len方法实现。len(dict)
- 字典排序:通过sorted方法对所有的key进行排序并以列表形式返回。 sorted(dict)
- 字典类型: 可用type函数查看字典中某一键值对的值的类型。type(dict['a'])
- 检查键值是否存在:要确定字典中是否存在指定的键,可使用 in 关键字。
-
1 >>> if 'a' in dict: 2 ... print("True")
-
- 遍历字典:可以使用 for 循环遍历字典,循环遍历字典时,返回值是字典的键,但也有返回值的方法。
- 逐个打印字典中的所有键名:
-
1 >>> for x in dict: 2 ... print(x)
-
- 逐个打印字典中的所有值:
-
1 >>> for x in dict: 2 ... print(dict[x])
-
- 还可以使用 values() 函数返回字典的值
-
1 >>> for x in dict.values(): 2 ... print(x)
-
- 通过使用 items() 函数遍历键和值
-
>>> for x,y in dict.items(): ... print(x,y)
-
- 逐个打印字典中的所有键名:
5 字典的常用内置方法
(1)fromkeys()方法
- 描述:Python 字典 fromkeys() 函数用于创建一个新字典,以序列 seq 中元素做字典的键,value 为字典所有键对应的初始值。
- 语法:dict.fromkeys(seq[, value])
- 参数:
- seq -- 字典键值列表。
- value -- 可选参数, 设置键序列(seq)的值,该参数只能有一个,并且所有键值列表对应的值都是它。
- 返回值:该方法返回一个新字典。
- 举例:
-
1 >>> dict1 = {} 2 >>> dict1.fromkeys((1,2,3)) 3 {1: None, 2: None, 3: None} 4 5 >>> dict1.fromkeys((1,2,3),'Number') 6 {1: 'Number', 2: 'Number', 3: 'Number'} 7 8 >>> dict1.fromkeys((1,2,3),('one','two','three')) 9 {1: ('one', 'two', 'three'), 2: ('one', 'two', 'three'), 3: ('one', 'two', 'three')} 10 11 >>> dict1.fromkeys((1,3),'数字') # 无法修改字典的值,会返回一个新的字典 12 {1: '数字', 3: '数字'}
(2)keys()方法
- 描述:Python 字典(Dictionary) keys() 函数以列表返回一个字典所有的键。
- 语法:dict.keys()
- 参数:无参数
- 返回值:返回一个字典所有的键。
- 举例:
-
1 >>> dict1 = {'a':1,'b':2,'c':3} 2 >>> dict1.keys() 3 dict_keys(['a', 'b', 'c'])
(3)values()方法
- 描述:Python 字典(Dictionary) values() 函数以列表返回字典中的所有值。
- 语法:dict.values()
- 参数:无参数
- 返回值:返回字典中的所有值。
- 举例:
-
1 >>> dict1.values() 2 dict_values([1, 2, 3])
(4)items()方法
- 描述:Python 字典(Dictionary) items() 函数以列表返回可遍历的(键, 值) 元组数组。
- 语法:dict.items()
- 参数:无参数
- 返回值:返回可遍历的(键, 值) 元组数组。
- 举例:
-
1 >>> dict1.items() 2 dict_items([('a', 1), ('b', 2), ('c', 3)])
(5)get()方法
- 描述:Python 字典(Dictionary) get() 函数返回指定键的值,如果值不在字典中返回默认值。
- 语法:dict.get(key, default=None)
- 参数:
- key -- 字典中要查找的键。
- default -- 如果指定键的值不存在时,返回该默认值。
- 返回值:返回指定键的值,如果值不在字典中返回默认值None。
- 举例:>>> dict1.get('a')
(6)clear()方法:
- 描述:Python 字典(Dictionary) clear() 函数用于删除字典内所有元素。
- 语法:dict.clear()
- 参数:无参数
- 返回值:该函数没有任何返回值。
- 举例:
-
1 >>> dict1.clear() 2 >>> dict1 3 {}
(7)copy()方法
- 描述:Python 字典(Dictionary) copy() 函数返回一个字典的浅复制。
- 浅复制和赋值是不一样的,由以下例子可看出:dict3和dict2的id是不一样的。
-
1 >>> dict1 = {'a':1,'b':2,'c':3} 2 >>> dict2 = dict1 3 >>> dict3 = dict1.copy() 4 >>> id(dict1) 5 216304025856 6 >>> id(dict2) 7 216304025856 8 >>> id(dict3) 9 216275514752
- 关于浅复制和深复制的内容,见网页:https://www.runoob.com/w3cnote/python-understanding-dict-copy-shallow-or-deep.html
- 语法:dict.copy()
- 参数:无参数
- 返回值:返回一个字典的浅复制。
- 举例:
-
1 >>> dict1.clear() 2 >>> dict1 3 {} 4 >>> dict2 5 {} 6 >>> dict3 7 {'a': 1, 'b': 2, 'c': 3}
(8)pop()方法
- 描述:Python 字典 pop() 方法删除字典给定键 key 及对应的值,返回值为被删除的值。key 值必须给出。 否则,返回 default 值。
- 语法:dict.pop(key[,default])
- 参数:
- key: 要删除的键值
- default: 如果没有 key,返回 default 值
- 返回值:返回被删除的值。
- 举例:
-
1 >>> dict3 2 {'a': 1, 'b': 2, 'c': 3} 3 >>> dict3.pop('c') 4 3 5 >>> dict3 6 {'a': 1, 'b': 2}
(9)popitem()方法
- 描述:Python 字典 popitem() 方法返回并删除字典中的最后一对键和值。如果字典已经为空,却调用了此方法,就报出 KeyError 异常。
- 语法:dict.popitem()
- 参数:无参数
- 返回值:返回一个键值对(key,value)形式。
-
1 >>> dict3 2 {'a': 1, 'b': 2} 3 >>> dict3.popitem() 4 ('b', 2) 5 >>> dict3 6 {'a': 1}
(10)setdefault()方法
- 描述:Python 字典 setdefault() 函数和 get()方法 类似, 如果键不存在于字典中,将会添加键并将值设为默认值/设置的值。
- 语法:dict.setdefault(key, default=None)
- 参数:
- key -- 查找的键值。
- default -- 键不存在时,设置的默认键值。
- 返回值:如果字典中包含有给定键,则返回该键对应的值,否则返回为该键设置的值
- 举例:
-
1 >>> dict3 = {'a':1} 2 >>> print(dict3.setdefault('b')) 3 None 4 >>> dict3 5 {'a': 1, 'b': None} 6 >>> print(dict3.setdefault('a',3)) 7 1 8 >>> print(dict3.setdefault('c',3)) 9 3
(11)update()方法
- 描述:Python 字典(Dictionary) update() 函数把字典dict2的键/值对更新到dict1里。
- 语法:dict1.update(dict2)
- 参数:dict2 -- 添加到指定字典dict1里的字典。
- 返回值:该方法没有任何返回值。
- 举例:
-
1 >>> dict3 2 {'a': 1, 'b': None, 'c': 3} 3 >>> dict1 = {'d':4,'e':5} 4 >>> dict3.update(dict1) 5 >>> dict3 6 {'a': 1, 'b': None, 'c': 3, 'd': 4, 'e': 5} 7 >>> dict1 8 {'d': 4, 'e': 5}
6 字典和列表的对比
字典的特殊性在于,内部存放的顺序与key存入的顺序没有任何关系,因为它本身就是无序的。字典与列表对比特点如下:
- 字典:
- 查找和插入的速度极快,不会随着key的增加而变慢
- 需要占用大量的内存(浪费比较多)
- 列表:
- 查找和插入的时间随着元素的增加而增加
- 占用空间小,内存浪费少
综上所述,字典是在用空间换取时间(执行效率)。
二 课后习题
(一)测试题部分
第25讲:
0. 当你听到小伙伴们在谈论“映射”、“哈希”、“散列”或者“关系数组”的时候,事实上他们就是在讨论什么呢?
答:是的,事实上他们就是在讨论我们这一讲介绍的“字典”,都是一个概念!
1. 尝试一下将数据('F': 70, 'C': 67, 'h': 104, 'i': 105, 's': 115)创建为一个字典并访问键 'C' 对应的值?
1 >>> MyDict = dict((('F', 70), ('i',105), ('s',115), ('h',104), ('C',67))) 2 >>> MyDict_2 = {'F':70, 'i':105, 's':115, 'h':104, 'C':67} 3 >>> type(MyDict) 4 <class 'dict'> 5 >>> type(MyDict_2) 6 <class 'dict'> 7 >>> MyDict['C'] 8 67
2. 用方括号(“[]”)括起来的数据我们叫列表,那么使用大括号(“{}”)括起来的数据我们就叫字典,对吗?
1 >>> NotADict = {1, 2, 3, 4, 5} 2 >>> type(NotADict) 3 <class 'set'>
不难发现,虽然我们用大括号(“{}”)把一些数据括起来了,但由于没有反映出这些数据有映射的关系,所以创建出来的不是字典,而是叫’set’的东西,那’set’到底又是啥玩意儿呢?请看第027讲 | 集合:在我的世界里,你就是唯一!
3. 你如何理解有些东西字典做得到,但“万能的”列表却难以实现?
1 >>> brand = ['李宁', '耐克', '阿迪达斯', '鱼C工作室'] 2 >>> slogan = ['一切皆有可能', 'Just do it', 'Impossible is nothing', '让编程改变世界'] 3 >>> print('鱼C工作室的口号是:', slogan[brand.index('鱼C工作室')]) 4 鱼C工作室的口号是: 让编程改变世界
列表brand、slogan的索引和相对的值是没有任何关系的,我们可以看出唯一有联系的就是两个列表间,索引号相同的元素是有关系的(品牌对应口号嘛),所以这里我们通过brand.index('鱼C工作室')这样的语句,间接的实现通过品牌查找对应的口号的功能。
这确实是一种可实现方法,呃……但用起来呢,多少有些别扭,效率还不高咧。况且Python是以简洁为主,这样子的实现肯定是不能让人满意的,所以呢,我们需要有字典这种映射类型的出现:
1 >>> dict1 = {'李宁':'一切皆有可能', '耐克':'Just do it', '阿迪达斯':'Impossible is nothing', '鱼C工作室':'让编程改变世界'} 2 >>> print('鱼C工作室的口号是:', dict1['鱼C工作室']) 3 鱼C工作室的口号是: 让编程改变世界
4. 下边这些代码,他们都在执行一样的操作吗?你看得出差别吗?
1 >>> a = dict(one=1, two=2, three=3) 2 >>> b = {'one': 1, 'two': 2, 'three': 3} 3 >>> c = dict(zip(['one', 'two', 'three'], [1, 2, 3])) 4 >>> d = dict([('two', 2), ('one', 1), ('three', 3)]) 5 >>> e = dict({'three': 3, 'one': 1, 'two': 2})
答:是的,他们都在创建字典:a = dict(one=1, two=2, three=3),没有差别
5. 如图,你可以推测出打了马赛克部分的代码吗?
嗯,我很傻的用分片的方式一个一个写的,我好呆。。。(data[0:3],data[5:7],data[9:])
1 data = "1000,小甲鱼,男" 2 MyDict = {} 3 # 还记得字符串的分割方法吧,别学过就忘啦^_^ 4 (MyDict['id'], MyDict['name'], MyDict['sex']) = data.split(',') 5 6 print("ID: " + MyDict['id']) 7 print("Name: " + MyDict['name']) 8 print("Sex " + MyDict['sex'])
第26讲:
0. Python的字典是否支持一键(Key)多值(Value)?
答:不支持,对相同的键再次赋值会将上一次的值直接覆盖。
1. 在字典中,如果试图为一个不存在的键(Key)赋值会怎样?
答:会自动创建对应的键(Key)并添加相应的值(Value)进去。(具体原理可以参考第3题的“扩展阅读”部分)
2. 成员资格操作符(in和not in)可以检查一个元素是否存在序列中,当然也可以用来检查一个键(Key)是否存在字典中,那么请问哪种的检查效率更高些?为什么?
答:在字典中检查键(Key)是否存在比在序列中检查指定元素是否存在更高效。因为字典的原理是使用哈希算法存储,一步到位,不需要使用查找算法进行匹配,因此时间复杂度是O(1),效率非常高。(关于如何使用哈希算法存储的具体原理可以参考第3题的“扩展阅读”部分)
3. Python对键(Key)和值(Value)有没有类型限制?
答:Python对键的要求相对要严格一些,要求它们必须是可哈希(Hash)的对象,不能是可变类型(包括变量、列表、字典本身等)。
但是Python对值是没有任何限制的,它们可以是任意的Python对象。
如果不清楚哈希原理以及字典的存放原理的童鞋,推荐阅读下小甲鱼帮你整理的这篇文章:你知道Python的字典(Dict)是如何存储的吗?(http://bbs.fishc.com/thread-45016-1-1.html)
4. 请目测下边代码执行后,字典dict1的内容是什么?
1 >>> dict1.fromkeys((1, 2, 3), ('one', 'two', 'three')) 2 >>> dict1.fromkeys((1, 3), '数字')
答:执行完成后,字典dict1的内容是:{1: '数字', 3: '数字'}
这里要注意的是,fromkeys方法是直接创建一个新的字典,不要试图使用它来修改一个原有的字典,因为它会直接无情的用把整个字典给覆盖掉。
5. 如果你需要将字典dict1 = {1: 'one', 2: 'two', 3: 'three'}拷贝到dict2,你应该怎么做?
答:可以利用字典的copy()方法:dict2 = dict1.copy(),在其他语言转移到Python小伙伴们刚开始可能会习惯性的直接用赋值的方法(dict2 = dict1),这样子做在Python中只是将对象的引用拷贝过去而已。
(二)动动手部分:
第25讲:
0. 尝试利用字典的特性编写一个通讯录程序吧!
我的代码:(感觉我的代码比小甲鱼的代码要聪明一点,哈哈哈哈)
1 # -*- coding:utf-8 -*- 2 def printInfo(): 3 print("|--- 欢迎进入通讯录程序 ---|") 4 print("|--- 1:查询联系人资料 ---|") 5 print("|--- 2:插入新的联系人 ---|") 6 print("|--- 3:删除已有联系人 ---|") 7 print("|--- 4:退出通讯录程序 ---|") 8 print() 9 10 def find(addressDict): 11 print(addressDict) 12 name = input("请输入联系人姓名:") 13 if name in addressDict: 14 print(f"{name}:{addressDict[name]}") 15 return addressDict 16 else: 17 print("用户不存在,请重新选择相应操作!") 18 return addressDict 19 20 def insert(addressDict): 21 name = input("请输入联系人姓名:") 22 if name in addressDict: 23 print("您输入的姓名在通讯录中已存在",end = '') 24 print(f"-->> {name}:{addressDict[name]}") 25 i= input("是否修改用户资料(YES/NO):") 26 if i == YES: 27 addressDict[name]=input("请输入用户联系电话:") 28 return addressDict 29 else: 30 return main() 31 return addressDict 32 else: 33 addressDict[name]=input("请输入用户联系电话:") 34 return addressDict 35 36 def delete(addressDict): 37 name = input("请输入联系人姓名:") 38 if name in addressDict: 39 del addressDict[name] 40 return addressDict 41 else: 42 print("用户不存在,请重新选择相应操作!") 43 return addressDict 44 45 def over(): 46 print("|--- 感谢使用通讯录程序 ---|") 47 exit() 48 49 def main(): 50 addressDict = {} 51 printInfo() 52 while 1: 53 print(f"addressDict = {addressDict}") 54 num = int(input("请输入相关的指令代码:")) 55 56 if num == 1: 57 find(addressDict) 58 elif num == 2: 59 insert(addressDict) 60 elif num == 3: 61 delete(addressDict) 62 elif num == 4: 63 over() 64 else: 65 print("输入错误,请重新输入数字:1或2或3或4 选择相应的操作") 66 print() 67 68 main()
小甲鱼的代码:
1 print('|--- 欢迎进入通讯录程序 ---|') 2 print('|--- 1:查询联系人资料 ---|') 3 print('|--- 2:插入新的联系人 ---|') 4 print('|--- 3:删除已有联系人 ---|') 5 print('|--- 4:退出通讯录程序 ---|') 6 7 contacts = dict() 8 9 while 1: 10 instr = int(input('\n请输入相关的指令代码:')) 11 12 if instr == 1: 13 name = input('请输入联系人姓名:') 14 if name in contacts: 15 print(name + ' : ' + contacts[name]) 16 else: 17 print('您输入的姓名不再通讯录中!') 18 19 if instr == 2: 20 name = input('请输入联系人姓名:') 21 if name in contacts: 22 print('您输入的姓名在通讯录中已存在 -->> ', end='') 23 print(name + ' : ' + contacts[name]) 24 if input('是否修改用户资料(YES/NO):') == 'YES': 25 contacts[name] = input('请输入用户联系电话:') 26 else: 27 contacts[name] = input('请输入用户联系电话:') 28 29 if instr == 3: 30 name = input('请输入联系人姓名:') 31 if name in contacts: 32 del(contacts[name]) # 也可以使用dict.pop() 33 else: 34 print('您输入的联系人不存在。') 35 36 if instr == 4: 37 break 38 39 print('|--- 感谢使用通讯录程序 ---|')
第·26讲:
0. 尝试编写一个用户登录程序(这次尝试将功能封装成函数)
小甲鱼代码:
1 user_data = {} 2 3 def new_user(): 4 prompt = '请输入用户名:' 5 while True: 6 name = input(prompt) 7 if name in user_data: 8 prompt = '此用户名已经被使用,请重新输入:' 9 continue 10 else: 11 break 12 13 passwd = input('请输入密码:') 14 user_data[name] = passwd 15 print('注册成功,赶紧试试登录吧^_^') 16 17 def old_user(): 18 prompt = '请输入用户名:' 19 while True: 20 name = input(prompt) 21 if name not in user_data: 22 prompt = '您输入的用户名不存在,请重新输入:' 23 continue 24 else: 25 break 26 27 passwd = input('请输入密码:') 28 pwd = user_data.get(name) 29 if passwd == pwd: 30 print('欢迎进入XXOO系统,请点右上角的X结束程序!') 31 else: 32 print('密码错误!') 33 34 def showmenu(): 35 prompt = ''' 36 |--- 新建用户:N/n ---| 37 |--- 登录账号:E/e ---| 38 |--- 推出程序:Q/q ---| 39 |--- 请输入指令代码:''' 40 41 while True: 42 chosen = False 43 while not chosen: 44 choice = input(prompt) 45 if choice not in 'NnEeQq': 46 print('您输入的指令代码错误,请重新输入:') 47 else: 48 chosen = True 49 50 if choice == 'q' or choice == 'Q': 51 break 52 if choice == 'n' or choice == 'N': 53 new_user() 54 if choice == 'e' or choice == 'E': 55 old_user() 56 57 showmenu()