开源图书《Python完全自学教程》第5.1.3节
5.1.3 字典的方法
依据第4章学习字符串和列表的过程推测字典对象也会有很多方法等待学习。
1. 读取值的方法
在5.1.2节中,曾使用 d[k]
读取了字典中已经存在的键值对的值,例如:
>>> d = {"name": "laoqi", "city": ['shanghai', 'soochow', 'hangzhou']}
>>> d['city']
['shanghai', 'soochow', 'hangzhou']
如果 d[k]
中的 k
不在字典中,会怎样?
>>> 'age' in d
False
>>> d['age']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'age'
在程序中,如果遇到这个错误,程序就会中止运行,能不能对这种情况进行处理?Python 的字典方法中给出两种处理方案。
-
字典的 get()
方法
字典的 get()
方法帮助文档这样描述:
get(key, default=None, /) method of builtins.dict instance
Return the value for key if key is in the dictionary, else default.
在 get()
的参数中,key
表示键——对此很好理解,要根据键读取值,必然要告诉此方法键是什么;还有一个关键词参数 default=None
,默认值是 None
,也可以设置为任何其他值。
>>> d.get('name') # (4)
'laoqi'
键 'name'
存在于字典 d
中,注释(4)理所应当返回它所在键值对的值,这就是帮助文档中所说的“ Return the value for key if key is in the dictionary ”。
>>> d.get('age')
字典中没有键 'age'
,但这里没有报错,也没有返回值——根据以往的经验,应该是返回了 None
。这就是帮助文档中后半句“else default”的含义,且 default
的默认值是 None
。
>>> d.get('age', 28) # (5)
28
注释(5)中设置 default
的值是 28
,于是返回了此值。
-
字典的 setdefault()
方法
还是先看 setdefault()
方法的帮助文档:
setdefault(key, default=None, /) method of builtins.dict instance
Insert key with a value of default if key is not in the dictionary.
Return the value for key if key is in the dictionary, else default.
与 get()
方法的文档比较:两个方法的参数形式一致;setdefault()
方法说明中的第二句与 get()
方法中的说明一致;下面就重点看看 get()
方法中没有的第一句。但是,在此之前,先要解决一个非常严峻的问题:官方文档都是英文的,如果英文比较差,看不懂怎么办?下面就向读者秘密地介绍一项绝技——之所以将这个绝技很隐蔽地在这里介绍,是因为只有真正的读者才能认真地看到此处。
此绝技就是两个字:翻译。不要急着评论,不是让你打开某个翻译软件进行翻译,而是要非常“程序地”翻译。
首先从交互模式中退回到终端,并执行图5-1-2所示的操作。输入指令并执行后,会自动安装,一般需要等待一会。
如果最终看到了下面的内容,则说明安装成功:
Successfully installed appdirs-1.4.4 certifi-2021.5.30 cfgv-3.3.0 chardet-4.0.0 click-8.0.1 distlib-0.3.2 filelock-3.0.12 identify-2.2.7 idna-2.10 lxml-4.6.3 nodeenv-1.6.0 packaging-20.9 pluggy-0.13.1 pre-commit-2.13.0 py-1.10.0 pyparsing-2.4.7 pyyaml-5.4.1 requests-2.25.1 toml-0.10.2 tox-3.23.1 translate-3.5.0 urllib3-1.26.5 virtualenv-20.4.7
然后,继续在终端(不要进入到交互模式),按照下面的方式执行一条翻译指令:
% translate-cli -t zh "Insert key with a value of default if key is not in the dictionary."
返回内容为:
Translation: 如果键不在字典中,则插入值为 default 的键。
-------------------------
Translated by: MyMemory
这种非常“程序地”翻译,非本书真正读者是不告诉他的——请守口如瓶。
这样,我们就全面理解了 setdefault()
的含义。下面通过操作体会一番(进入到交互模式):
>>> d = {"name": "laoqi", "city": ['shanghai', 'soochow', 'hangzhou']}
>>> d.setdefault('name') # 同 get() 方法
'laoqi'
>>> d.setdefault('age') # (6)
对于注释(6),按照帮助文档中的描述,应该返回了 default
的值 None
,并且将以 'age'
为键 default
的值为值的键值对“ 'age': None
”插入到字典中。所以,此时字典成为:
>>> d
{'name': 'laoqi', 'city': ['shanghai', 'soochow', 'hangzhou'], 'age': None}
如果 default
的值不为 None
,则:
>>> d.setdefault('years', 28)
28
>>> d
{'name': 'laoqi', 'city': ['shanghai', 'soochow', 'hangzhou'], 'age': None, 'years': 28}
2. 视图对象
Python 字典对象有三个分别读取键、值和键值对的方法:
>>> dct = {"book": "learn python", "price": 99}
>>> dct.keys()
dict_keys(['book', 'price'])
>>> dct.values()
dict_values(['learn python', 99])
>>> dct.items()
dict_items([('book', 'learn python'), ('price', 99)])
以上操作的返回值,不是前面学过的列表,在 Python 中称之为视图对象( View Object )——这是 Python 3 中才引入的,在 Python 2 中没有此名词。它有什么特点呢?请特别观察如下操作:
>>> v = dct.values()
>>> v
dict_values(['learn python', 99])
变量 v
引用了 dct.values()
返回的视图对象。下面修改字典 dct
中键值对 "price": 99
的值,将 99
改为 89
:
>>> dct['price'] = 89
>>> dct
{'book': 'learn python', 'price': 89}
变量 dct
引用的字典对象中的键值对已经改为“ 'price': 89
”,再来看此时变量 v
所引用的对象中,原来的 99
是否会变成 89
——注意,没有再次执行 v = dct.values()
。
>>> v
dict_values(['learn python', 89])
由此,显示了视图对象的特点,即字典改变,视图也随之变化。
能不能通过修改视图对象的成员来改变字典呢?可以试试:
>>> v[1] = 79 # (7)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'dict_values' object does not support item assignment
试一试的结果说明,不能使用注释(7)的方式修改视图内的成员。但是,可以用 list()
函数将视图对象转化为列表:
>>> vlst = list(v)
>>> vlst
['learn python', 89]
>>> vlst[1] = 79
>>> vlst
['learn python', 79]
>>> v
dict_values(['learn python', 89])
变量 vlst
引用的列表相对原来的视图对象而言是一个新的对象,固然能够通过它修改其成员,但不会影响原来的视图对象。
建议读者仿照上述内容,再对字典的 items()
和 keys()
两方法进行操练。三者含义相同,不赘述。
字典的这三个方法所得到的视图对象,也是可迭代对象,在第6章6.3.1节还会用到它们。
3. 增加键值对
向字典中增加键值对的一种常用方法是前面已经介绍过的“ d[k] = v
”,但这种方式只能一次增加一个键值对。
字典中还有一个名为 update()
的方法,它则能实现批量“更新”字典。以如下两个字典为例:
>>> dct
{'book': 'learn python', 'price': 89}
>>> author = {'name': 'laoqi', 'age': 28}
>>> id(dct)
140554197614336
>>> dct.update(author) # (8)
>>> id(dct)
140554197614336
>>> dct
{'book': 'learn python', 'price': 89, 'name': 'laoqi', 'age': 28}
注释(8)以字典 author
为参数,用它“更新”了字典 dct
——只是“更新”,并没有生成新的对象,还要注意,注释(8)没有返回值。
除了像注释(8)那样用字典作为 update()
方法的参数之外,还可以使用如下参数“更新”字典。
>>> dct.update([('lang', 'python'), ('pub', 'PHEI')]) # (9)
>>> dct
{'book': 'learn python', 'price': 89, 'name': 'laoqi',
'age': 28, 'lang': 'python', 'pub': 'PHEI'}
注释(9)中以 [('lang', 'python'), ('pub', 'PHEI')]
为 update()
的参数,仔细观察此参数的结构,它也表示了一种对应关系,等效于 {'lang': 'python', 'pub': 'PHEI'}
。把它们作为 dict()
函数的参数,同样能创建字典。
>>> dict([('lang', 'python'), ('pub', 'PHEI')])
{'lang': 'python', 'pub': 'PHEI'}
用来表示对应关系的形式,除了字典和类似 [('lang', 'python'), ('pub', 'PHEI')]
的结构之外,还可以使用“关键词参数”,所以方法 update()
的参数也能用如下代码所示的方式提供。
>>> d = {'a': 10}
>>> d.update(b=100, c=200) # (10)
>>> d
{'a': 10, 'b': 100, 'c': 200}
注释(10)中用关键词参数表示了对应关系,也能实现对原字典的“更新”。
经过以上操作之后,再来阅读 update()
方法的帮助文档,定会对其含义有了深刻的理解。
update(...) method of builtins.dict instance
D.update([E, ]**F) -> None. Update D from dict/iterable E and F.
If E is present and has a .keys() method, then does: for k in E: D[k] = E[k]
If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v
In either case, this is followed by: for k in F: D[k] = F[k]
注释(8)(9)(10)的三个操作,正好对应帮助文档中所说的三种情况:
-
注释(8)中的参数是字典,字典有 keys()
方法; -
注释(9)中的参数是列表(列表的成员是元组),且可迭代,但没有 keys()
方法; -
注释(10)的关键词参数,对应于帮助文档中的“ **F
”(这里的**
不是数学运算符号,表示F
收集关键词参数,参阅第7章7.2.1节)。
所以,认真地、耐心的读文档是多么重要呀,无怪乎子曰:“我非生而知之者,好古,敏以求之者也。”
4. 删除键值对
因为列表和字典中都有 pop()
方法,根据“温故而知新”的教诲,建议读者复习第4章4.3.2节中对此方法的介绍,并理解下述操作。
>>> lst = ['a', 'b', 'c']
>>> lst.pop(1)
'b'
>>> lst
['a', 'c']
>>> lst.pop()
'c'
>>> lst
['a']
与之对比,字典的 pop()
方法肯定不会以“索引”为参数——字典没有索引,根据5.1.1节所学知识,读者肯定能想到,其参数必然是键值对的键。下面就检验此猜测:
>>> dct # 注释(9)使用过的字典,读者在调试的时候,内存中若没有,可以新建
{'book': 'learn python', 'price': 89, 'name': 'laoqi',
'age': 28, 'lang': 'python', 'pub': 'PHEI'}
>>> dct.pop('price') # (11)
89
注释(11)的操作结果证实了猜测。继续与列表中的同名方法类比,能不能如同 lst.pop()
那样,不提供任何参数的时候,删除“最后一个”成员呢?
>>> dct.pop() # (12)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pop expected at least 1 argument, got 0
不能。并且错误提示告诉我们,字典的 pop()
方法最少要有一个参数——言外之意,除了像注释(11)那样提供一个键作为参数之外,还可以有别的参数。看来光凭猜测,还不能解决深层次问题,必须要认真地、耐心地阅读文档。
pop(...) method of builtins.dict instance
D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
If key is not found, default is returned if given, otherwise KeyError is raised
由此文档可知,使用字典的 pop()
方法必须知悉如下事项:
-
D.pop(k[,d])
中的k
说明必须以键为参数,且不可省略,所以注释(12)的操作是不被允许的。 -
D.pop(k[,d])
中的d
是可选项。如果提供值,当字典中没有要删除的k
时,就会返回d
的值(If key is not found, default is returned if given);如果不提供d
的值,此时就会返抛出KeyError
异常(otherwise KeyError is raised)。例如下面的注释(13)(14)的操作。
>>> dct.pop('price') # (13)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'price'
>>> dct.pop('pirce', 314) # (14)
314
>>> dct
{'book': 'learn python', 'name': 'laoqi',
'age': 28, 'lang': 'python', 'pub': 'PHEI'}
注释(12)显示了字典和列表的 pop()
方法的一个差异,但是字典中的另外一个方法则酷似列表中的 lst.pop()
删除最后一个成员。
>>> dct
{'book': 'learn python', 'name': 'laoqi',
'age': 28, 'lang': 'python', 'pub': 'PHEI'}
>>> dct.popitem() # (15)
('pub', 'PHEI')
>>> dct
{'book': 'learn python', 'name': 'laoqi', 'age': 28, 'lang': 'python'}
字典的 popitem()
方法,如注释(15),调用的时候参数为空,删除了原字典最后一对键值对,且将其以元组的形式返回。此方法的帮助文档很值得我们阅读,因为其中提到了一个新的术语:LIFO。
popitem() method of builtins.dict instance
Remove and return a (key, value) pair as a 2-tuple.
Pairs are returned in LIFO (last-in, first-out) order.
Raises KeyError if the dict is empty.
LIFO ,即“Last in, First out”,译为“后进先出”,这是计算机科学中插入、删除数据一种原则,例如,一种名为栈( Stack )的数据结构,只能在栈顶执行插入和删除操作。先进入的数据就被压入到栈底,后进入的在栈顶;执行删除操作时,就要先删除位于栈顶的后进入的操作,故“后进先出”(如图5-1-3所示)。
虽然 Python 的字典与栈不同,但它也遵循了 LIFO 原则。读者观察之前所有创建字典和向字典中增加成员的操作,是不是先加入的键值对在左侧,后加入的在右侧?这种排序,其实是从 Python 3.6 开始具有的,在 Python 3.6 之前,字典中的键值对是“无序”的。按照本书学习要求,使用的是 Python 3.9 。
借着顺序问题,顺便看看字典作为内置函数 sorted()
参数的结果:
>>> dct
{'book': 'learn python', 'name': 'laoqi', 'age': 28, 'lang': 'python'}
>>> sorted(dct) # (16)
['age', 'book', 'lang', 'name']
注释(16)返回了排序结果:字典中键的顺序。有了键的顺序,就可以更进一步得到对应的值了。在第4章4.3.2节将 sorted()
函数用之于列表,对于序列进行排序是顺理成章的事情。而字典,固然它的成员符合 LIFO 原则,毕竟是“键值对的集合”——集合没有顺序(参阅5.2节)。字典之所以能作为 sorted()
的参数,原因就是字典是可迭代对象(请思考,怎么判断字典是不是可迭代对象。参阅第4章4.3.2节),sorted()
函数对参数的要求即如此。注意,字典没有 sort()
方法——不同于列表。
第三个字典对象删除成员的方法 clear()
,和列表中同名方法无差别,都是将容器清空。
>>> dct
{'book': 'learn python', 'name': 'laoqi', 'age': 28, 'lang': 'python'}
>>> id(dct)
140554197614336
>>> dct.clear() # 注意观察此方法前后字典的内存地址
>>> id(dct)
140554197614336
★自学建议
本节内容是继第3章3.3节的【自学建议】之后,进一步以实际学习过程为例,演示了如何边学习边理解帮助文档,依靠帮助文档加深对操作的理解。
这不是“手把手”“保姆式”的教授方法,会有人对本节内容非常反感——按照常理推断,数量不会很少,毕竟被“灌输”会有一种省时省力地接受所谓“干货”的虚假愉悦感和虚假满足感。那么,本书或许很不适合此类读者,恭请尽快寻找其他读物,以免耽误锦绣前程。
”
更多《Python完全自学教程》内容,请点击【阅读原文】