1 . 字典
字典是通过名称来访问其各个值的数据结构,这种数据结构称为映射(mapping)。字典是Python中唯一的内置映射类型,其中的值不按顺序排列,而是存储在键下。键可能是数、字符串或元组。
字典的用途
字典的名称指出了这种数据结构的用途。普通图书适合按从头到尾的顺序阅读,如果你愿意,可快速翻到任何一页,这有点像Python中的列表。字典(日常生活中的字典和Python字典)旨在让你能够轻松地找到特定的单词(键),以获悉其定义(值)。
在很多情况下,使用字典都比使用列表更合适。下面是Python字典的一些用途:
- 表示棋盘的状态,其中每个键都是由坐标组成的元组;
- 存储文件修改时间,其中的键为文件名;
- 数字电话/地址簿。
假设有如下名单:
>>> names = ['Alice', 'Beth', 'Cecil', 'Dee-Dee', 'Earl']
如果要创建一个小型数据库,在其中存储这些人的电话号码,该如何办呢?一种办法是再创建一个列表。假设只存储四位的分机号,这个列表将类似于:
>>> numbers = ['2341', '9102', '3158', '0142', '5551']
创建这些列表后,就可像下面这样查找Cecil的电话号码:
>>> numbers[names.index('Cecil')] '3158'
这可行,但不太实用。实际上,你希望能够像下面这样做:
>>> phonebook['Cecil'] '3158'
如何达成这个目标呢?只要phonebook
是个字典就行了。
创建和使用字典
字典以类似于下面的方式表示:
phonebook = {'Alice': '2341', 'Beth': '9102', 'Cecil': '3258'}
字典由键及其相应的值组成,这种键-值对称为项(item)。在前面的示例中,键为名字,而值为电话号码。每个键与其值之间都用冒号(:
)分隔,项之间用逗号分隔,而整个字典放在花括号内。空字典(没有任何项)用两个花括号表示,类似于下面这样:{}
。
注意 在字典(以及其他映射类型)中,键必须是独一无二的,而字典中的值无需如此。
函数dict
可使用函数dict
从其他映射(如其他字典)或键-值对序列创建字典。
与list
、tuple
和str
一样,dict
其实根本就不是函数,而是一个类。
>>> items = [('name', 'Gumby'), ('age', 42)] >>> d = dict(items) >>> d {'age': 42, 'name': 'Gumby'} >>> d['name'] 'Gumby'
还可使用关键字实参来调用这个函数,如下所示:
>>> d = dict(name='Gumby', age=42) >>> d {'age': 42, 'name': 'Gumby'}
基本的字典操作
字典的基本行为在很多方面都类似于序列。
len(d)
返回字典d
包含的项(键-值对)数。d[k]
返回与键k
相关联的值。d[k] = v
将值v
关联到键k
。del d[k]
删除键为k
的项。k in d
检查字典d
是否包含键为k
的项。
虽然字典和列表有多个相同之处,但也有一些重要的不同之处。
- 键的类型:字典中的键可以是整数,但并非必须是整数。字典中的键可以是任何不可变的类型,如浮点数(实数)、字符串或元组。
- 自动添加:即便是字典中原本没有的键,也可以给它赋值,这将在字典中创建一个新项。然而,如果不使用
append
或其他类似的方法,就不能给列表中没有的元素赋值。 - 成员资格:表达式
k in d
(其中d
是一个字典)查找的是键而不是值,而表达式v in l
(其中l
是一个列表)查找的是值而不是索引。这看似不太一致,但你习惯后就会觉得相当自然。毕竟如果字典包含指定的键,检查相应的值就很容易。
提示 相比于检查列表是否包含指定的值,检查字典是否包含指定的键的效率更高。数据结构越大,效率差距就越大。
前述第一点(键可以是任何不可变的类型)是字典的主要优点。第二点也很重要,下面的示例说明了这种差别:
>>> x = [] >>> x[42] = 'Foobar' Traceback (most recent call last): File "<stdin>", line 1, in ? IndexError: list assignment index out of range >>> x = {} >>> x[42] = 'Foobar' >>> x {42: 'Foobar'}
首先,我尝试将字符串'Foobar'
赋给一个空列表中索引为42的元素。这显然不可能,因为没有这样的元素。要让这种操作可行,初始化x
时,必须使用[None] * 43
之类的代码,而不能使用[]
。然而,接下来的尝试完全可行。这次我将'Foobar'
赋给一个空字典的键42;如你所见,这样做一点问题都没有:在这个字典中添加了一个新项,我得逞了。
将字符串格式设置功能用于字典
可使用字符串格式设置功能来设置值的格式,这些值是作为命名或非命名参数提供给方法format
的。在有些情况下,通过在字典中存储一系列命名的值,可让格式设置更容易些。例如,可在字典中包含各种信息,这样只需在格式字符串中提取所需的信息即可。为此,必须使用format_map
来指出你将通过一个映射来提供所需的信息。
>>> phonebook {'Beth': '9102', 'Alice': '2341', 'Cecil': '3258'} >>> "Cecil's phone number is {Cecil}.".format_map(phonebook) "Cecil's phone number is 3258."
字典方法:
方法clear
删除所有的字典项,这种操作是就地执行的(就像list.sort
一样),因此什么都不返回(或者说返回None
)。
>>> d = {} >>> d['name'] = 'Gumby' >>> d['age'] = 42 >>> d {'age': 42, 'name': 'Gumby'} >>> returned_value = d.clear() >>> d {} >>> print(returned_value) None
copy
方法copy
返回一个新字典,其包含的键-值对与原来的字典相同(这个方法执行的是浅复制,因为值本身是原件,而非副本)。
>>> x = {'username': 'admin', 'machines': ['foo', 'bar', 'baz']} >>> y = x.copy() >>> y['username'] = 'mlh' >>> y['machines'].remove('bar') >>> y {'username': 'mlh', 'machines': ['foo', 'baz']} >>> x {'username': 'admin', 'machines': ['foo', 'baz']}
如你所见,当替换副本中的值时,原件不受影响。然而,如果修改副本中的值(就地修改而不是替换),原件也将发生变化,因为原件指向的也是被修改的值(如这个示例中的'machines'
列表所示)。
为避免这种问题,一种办法是执行深复制,即同时复制值及其包含的所有值,等等。为此,可使用模块copy
中的函数deepcopy
。
>>> from copy import deepcopy >>> d = {} >>> d['names'] = ['Alfred', 'Bertrand'] >>> c = d.copy() >>> dc = deepcopy(d)
>>> d['names'].append('Clive')
>>> c
{'names': ['Alfred', 'Bertrand', 'Clive']}
>>> dc
{'names': ['Alfred', 'Bertrand']}
fromkeys
方法fromkeys
创建一个新字典,其中包含指定的键,且每个键对应的值都是None
。
>>> {}.fromkeys(['name', 'age']) {'age': None, 'name': None}
这个示例首先创建了一个空字典,再对其调用方法fromkeys
来创建另一个字典,这显得有点多余。你可以不这样做,而是直接对dict
(前面说过,dict
是所有字典所属的类型)
>>> dict.fromkeys(['name', 'age']) {'age': None, 'name': None}
如果你不想使用默认值None
,可提供特定的值。
>>> dict.fromkeys(['name', 'age'], '(unknown)') {'age': '(unknown)', 'name': '(unknown)'}
get
方法get
为访问字典项提供了宽松的环境。通常,如果你试图访问字典中没有的项,将引发错误。
>>> d = {} >>> print(d['name']) Traceback (most recent call last): File "<stdin>", line 1, in ? KeyError: 'name'
而使用get
不会这样:
>>> print(d.get('name')) None
如你所见,使用get
来访问不存在的键时,没有引发异常,而是返回None
。你可指定“默认”值,这样将返回你指定的值而不是None
。
>>> d.get('name', 'N/A') 'N/A'
如果字典包含指定的键,get
的作用将与普通字典查找相同。
>>> d['name'] = 'Eric' >>> d.get('name') 'Eric'
方法items
返回一个包含所有字典项的列表,其中每个元素都为(key, value)
的形式。字典项在列表中的排列顺序不确定。
>>> d = {'title': 'Python Web Site', 'url': 'http://www.python.org', 'spam': 0} >>> d.items() dict_items([('url', 'http://www.python.org'), ('spam', 0), ('title', 'Python Web Site')])
返回值属于一种名为字典视图的特殊类型。字典视图可用于迭代。另外,你还可确定其长度以及对其执行成员资格检查。
>>> it = d.items() >>> len(it) 3 >>> ('spam', 0) in it True
视图的一个优点是不复制,它们始终是底层字典的反映,即便你修改了底层字典亦如此。
>>> d['spam'] = 1 >>> ('spam', 0) in it False >>> d['spam'] = 0 >>> ('spam', 0) in it True
然而,如果你要将字典项复制到列表中(在较旧的Python版本中,方法items
就是这样做的),可自己动手做。
>>> list(d.items()) [('spam', 0), ('title', 'Python Web Site'), ('url', 'http://www.python.org')]
keys
方法keys
返回一个字典视图,其中包含指定字典中的键。
pop
方法pop
可用于获取与指定键相关联的值,并将该键-值对从字典中删除。
>>> d = {'x': 1, 'y': 2} >>> d.pop('x') 1 >>> d {'y': 2}
popitem
方法popitem
类似于list.pop
,但list.pop
弹出列表中的最后一个元素,而popitem
随机地弹出一个字典项,因为字典项的顺序是不确定的,没有“最后一个元素”的概念。如果你要以高效地方式逐个删除并处理所有字典项,这可能很有用,因为这样无需先获取键列表。
>>> d = {'url': 'http://www.python.org', 'spam': 0, 'title': 'Python Web Site'} >>> d.popitem() ('url', 'http://www.python.org') >>> d {'spam': 0, 'title': 'Python Web Site'}
虽然popitem
类似于列表方法pop
,但字典没有与append
(它在列表末尾添加一个元素)对应的方法。这是因为字典是无序的,类似的方法毫无意义。
提示 如果希望方法popitem
以可预测的顺序弹出字典项,请参阅模块collections
中的OrderedDict
类。
setdefault
方法setdefault
有点像get
,因为它也获取与指定键相关联的值,但除此之外,setdefault
还在字典不包含指定的键时,在字典中添加指定的键-值对。
>>> d = {} >>> d.setdefault('name', 'N/A') 'N/A' >>> d {'name': 'N/A'} >>> d['name'] = 'Gumby' >>> d.setdefault('name', 'N/A') 'Gumby' >>> d {'name': 'Gumby'}
如你所见,指定的键不存在时,setdefault
返回指定的值并相应地更新字典。如果指定的键存在,就返回其值,并保持字典不变。与get
一样,值是可选的;如果没有指定,默认为None
。
提示 如果希望有用于整个字典的全局默认值,请参阅模块collections
中的defaultdict
类。
update
方法update
使用一个字典中的项来更新另一个字典。
>>> d = { ... 'title': 'Python Web Site', ... 'url': 'http://www.python.org', ... 'changed': 'Mar 14 22:09:15 MET 2016' ... } >>> x = {'title': 'Python Language Website'} >>> d.update(x) >>> d {'url': 'http://www.python.org', 'changed': 'Mar 14 22:09:15 MET 2016', 'title': 'Python Language Website'}
对于通过参数提供的字典,将其项添加到当前字典中。如果当前字典包含键相同的项,就替换它。
可像调用前面讨论的函数dict
(类型构造函数)那样调用方法update
。这意味着调用update
时,可向它提供一个映射、一个由键-值对组成的序列(或其他可迭代对象)或关键字参数。
values
方法values
返回一个由字典中的值组成的字典视图。不同于方法keys
,方法values
返回的视图可能包含重复的值。
>>> d = {} >>> d[1] = 1 >>> d[2] = 2 >>> d[3] = 3 >>> d[4] = 1 >>> d.values() dict_values([1, 2, 3, 1])
小结:
- 映射:映射让你能够使用任何不可变的对象(最常用的是字符串和元组)来标识其元素。Python只有一种内置的映射类型,那就是字典。
- 将字符串格式设置功能用于字典:要对字典执行字符串格式设置操作,不能使用
format
和命名参数,而必须使用format_map
。 - 字典方法:字典有很多方法,这些方法的调用方式与列表和字符串的方法相同。