vlambda博客
学习文章列表

开源图书《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 1in <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所示的操作。输入指令并执行后,会自动安装,一般需要等待一会。

图5-1-2 安装 translate 库

如果最终看到了下面的内容,则说明安装成功:

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 1in <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 1in <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 1in <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所示)。

图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完全自学教程》内容,请点击【阅读原文】