《Python基础教程》笔记 第4章 字典:当索引行不通时
当需要通过编号来访问值时,列表很有用。本章将介绍一种可以通过名称来访问值的数据结构,称为映射(mapping)。Python中唯一的内置映射类型是字典。
4.1 字典的用途
字典(dictionary)(不管是现实中的还是Python中的)的结构让你能很容易地查找特定的单词(键)从而找到其定义(值)。
在很多情况下,字典都比列表更合适。例如,如果想创建一个小型数据库,在其中存储一些人的名字和对应的电话号码。一种方法是创建两个列表:
1
2
>>> names = ['Alice', 'Beth', 'Cecil', 'Dee-Dee', 'Earl']
>>> numbers = ['2341', '9102', '3158', '0142', '5551']
可以像这样查找Cecil的电话号码:
1
2
>>> numbers[names.index('Cecil')]
'3158'
这样虽然可行,但不太实用。你希望能够像下面这样做:
1
2
>>> phonebook['Cecil']
'3158'
如果phonebook
是字典就可以这样做。
4.2 创建和使用字典
字典像这样表示:
1
phonebook = {'Alice': '2341', 'Beth': '9102', 'Cecil': '3158'}
字典由键(key)和对应的值(value)构成的键值对组成,键值对也称为项(item)。在上面的例子中,名字是键,电话号码是值。每个键与其值之间用冒号(:
)分隔,项之间用逗号分隔,整个字典用花括号括起来。空字典用两个花括号表示:{}
。
注意,在映射中,键必须是唯一的,而值不需要是唯一的。
4.2.1 dict函数
可以使用dict()
函数从其他映射或键值对序列创建字典。
注:与list
、tuple
和str
一样,dict
并不是真正的函数,而是一个类。
1
2
3
4
5
6
>>> items = [('name', 'Gumby'), ('age', 42)]
>>> d = dict(items)
>>> d
{'name': 'Gumby', 'age': 42}
>>> d['name']
'Gumby'
也可以使用关键字参数,例如:
1
2
>>> dict(name='Gumby', age=42)
{'name': 'Gumby', 'age': 42}
还可以使用一个映射作为dict()
的参数,这将创建一个包含相同项的字典,另外也可以使用字典的copy()
方法。无参数的dict()
返回一个空字典。
4.2.2 基本字典操作
字典的基本操作与序列有些类似:
len(d)
返回d
中项(键值对)的数量d[k]
返回键k
关联的值,如果不存在则引发KeyError
d[k] = v
将值v
关联到键k
del d[k]
删除键为k
的项,如果不存在则引发KeyError
k in d
检查d
是否包含键k
然而,字典和列表有一些重要的区别:
- 键类型:字典的键可以是任何不可变类型,例如整数、浮点数、字符串或元组;而列表的索引必须是整数。
- 自动添加:即便是字典中原本没有的键,也可以给它赋值,这将创建一个新项;而不能给列表范围之外的索引赋值。
- 成员资格:表达式
k in d
(d
是字典)查找的是键而不是值,而表达式v in l
(l
是列表)查找的是值而不是索引。
注:字典的成员资格检查比列表更高效,前者是O(1)的,后者是O(n)的。
1
2
3
4
5
6
7
8
9
>>> x = []
>>> x[42] = 'Foobar'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list assignment index out of range
>>> x = {}
>>> x[42] = 'Foobar'
>>> x
{42: 'Foobar'}
代码清单4-1展示了电话簿示例的代码。
4.2.3 使用字典的字符串格式化
字符串的format_map()
方法通过一个映射来指定要格式化的值。
1
2
3
>>> phonebook = {'Beth': '9102', 'Alice': '2341', 'Cecil': '3258'}
>>> "Cecil's phone number is {Cecil}.".format_map(phonebook)
"Cecil's phone number is 3258."
这种字符串格式化在模板系统(在这里是HTML)中很有用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> template = '''<html>
... <head><title>{title}</title></head>
... <body>
... <h1>{title}</h1>
... <p>{text}</p>
... </body>'''
>>> data = {'title': 'My Home Page', 'text': 'Welcome to my home page!'}
>>> print(template.format_map(data))
<html>
<head><title>My Home Page</title></head>
<body>
<h1>My Home Page</h1>
<p>Welcome to my home page!</p>
</body>
4.2.4 字典方法
完整列表参考官方文档Mapping Types — dict。
clear
clear()
方法删除字典中所有的项。这是一个原地操作。
1
2
3
4
>>> d = {'age': 42, 'name': 'Gumby'}
>>> d.clear()
>>> d
{}
copy
copy()
方法返回一个具有相同键值对的新字典(浅拷贝,因为值本身没有没拷贝)。
1
2
3
4
5
6
7
8
>>> 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']}
可以看到,当替换副本中的值时,原对象不受影响。然而,如果修改副本中的值,原对象也将发生变化。
注:如1.4节所述,Python变量相当于C++的指针变量,“替换”=改变指针指向,“修改”=通过指针修改对象。在上面的示例中,字典x
和y
在内存中如下图所示:
避免这个问题的一种方法是使用深拷贝。为此,使用copy
模块中的deepcopy()
函数。
1
2
3
4
5
6
7
8
9
>>> from copy import deepcopy
>>> 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
)。
注:该方法是类方法,可以直接对类型dict
调用。
1
2
3
4
>>> dict.fromkeys(['name', 'age'])
{'name': None, 'age': None}
>>> dict.fromkeys(['name', 'age'], '(unknown)')
{'name': '(unknown)', 'age': '(unknown)'}
注意:该方法创建的字典的所有键都关联到同一个值对象,当值是可变类型时会导致意外的结果。例如:
1
2
3
4
5
6
>>> d = dict.fromkeys(['foo', 'bar', 'baz'], [])
>>> d
{'foo': [], 'bar': [], 'baz': []}
>>> d['foo'].append(42)
>>> d
{'foo': [42], 'bar': [42], 'baz': [42]}
为避免这一问题,应使用字典推导式(详见5.6节)。
1
2
3
4
5
6
>>> d = {k: [] for k in ['foo', 'bar', 'baz']}
>>> d
{'foo': [], 'bar': [], 'baz': []}
>>> d['foo'].append(42)
>>> d
{'foo': [42], 'bar': [], 'baz': []}
get
通常,如果试图访问字典中没有的项,将引发KeyError
。
1
2
3
4
5
>>> d = {}
>>> d['name']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'name'
使用get()
方法访问不存在的键时,不会引发异常,而是返回None
或者指定的“默认”值。
1
2
3
4
>>> print(d.get('name'))
None
>>> d.get('name', 'N/A')
'N/A'
如果键存在,则get()
与普通的字典查询一样。
1
2
3
>>> d['name'] = 'Eric'
>>> d.get('name')
'Eric'
代码清单4-2是代码清单4-1所示程序的修改版本,使用get()
方法访问“数据库”。
items
items()
方法返回一个包含所有字典项的列表,其中每一项的形式为(key, value)
。字典项没有特定的顺序。
1
2
3
>>> d = {'title': 'Python Web Site', 'url': 'http://www.python.org', 'spam': 0}
>>> d.items()
dict_items([('title', 'Python Web Site'), ('url', 'http://www.python.org'), ('spam', 0)])
返回值是一种叫做字典视图(dictionary view)的特殊类型。字典视图可用于迭代(详见第5章),还可以确定长度和成员资格检查。
1
2
3
4
5
>>> it = d.items()
>>> len(it)
3
>>> ('spam', 0) in it
True
视图的一个优点是没有拷贝,它们始终反映底层字典。
1
2
3
4
5
6
>>> d['spam'] = 1
>>> ('spam', 0) in it
False
>>> d['spam'] = 0
>>> ('spam', 0) in it
True
如果需要将字典项拷贝到列表,可以自己实现。
1
2
>>> list(d.items())
[('title', 'Python Web Site'), ('url', 'http://www.python.org'), ('spam', 0)]
keys
key()
方法返回字典的键的视图。
pop
pop()
方法用于获取给定的键对应的值,并将该键值对删除。如果键不存在则返回默认值,如果未指定默认值则引发KeyError
。
1
2
3
4
5
6
7
8
9
10
11
>>> d = {'x': 1, 'y': 2}
>>> d.pop('x')
1
>>> d
{'y': 2}
>>> d.pop('z')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'z'
>>> d.pop('z', 3)
3
popitem
popitem()
删除并返回一个字典项,如果字典为空则引发KeyError
。
注:在早期版本中,该方法弹出随机的项。从3.7版本开始,该方法按后进先出(LIFO)的顺序返回项。
1
2
3
4
5
>>> d = {'url': 'http://www.python.org', 'spam': 0, 'title': 'Python Web Site'}
>>> d.popitem()
('title', 'Python Web Site')
>>> d
{'url': 'http://www.python.org', 'spam': 0}
注:如果希望popitem()
遵循可预测的顺序,使用collections
模块的OrderedDict
类。
setdefault
setdefault()
方法返回与给定的键关联的值。如果键不存在,则将键对应的值设置为给定值(默认为None
),并返回这个值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> d = {}
>>> d.setdefault('name', 'N/A')
'N/A'
>>> d
{'name': 'N/A'}
>>> d['name'] = 'Gumby'
>>> d.setdefault('name', 'N/A')
'Gumby'
>>> d
{'name': 'Gumby'}
>>> print(d.setdefault('age'))
None
>>> d
{'name': 'Gumby', 'age': None}
注:该方法大致等价于
1
2
3
4
def setdefault(self, key, default=None):
if key not in self:
self[key] = default
return self[key]
update
update()
方法使用另一个字典的项来更新该字典。参数提供的字典中的项将被添加到该字典中,覆盖具有相同键的项。
1
2
3
4
5
6
7
8
9
>>> 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
{'title': 'Python Language Website', 'url': 'http://www.python.org', 'changed': 'Mar 14 22:09:15 MET 2016'}
update()
方法与dict()
函数一样,参数可以是一个映射、一个键值对序列或关键字参数。
注:Python 3.9引入了一个新的字典运算符|=
,d |= other
等价于d.update(other)
。
values
values()
方法返回字典的值的视图。与keys()
不同,values()
返回的视图可能包含重复值。
1
2
3
>>> d = {1: 1, 2: 2, 3: 3, 4: 1}
>>> d.values()
dict_values([1, 2, 3, 1])