Python学习第4章-当索引行不通时:字典

第四章-当索引行不通时:字典

需要将一系列值组合成数据结构并通过编号来访问各个值时,列表很有用。本章介绍一种可通过名称来访问其各个值的数据结构。这种数据结构称为映射(mapping)。字典是Python中唯一的内置映射类型,其中的值不按顺序排列,而是储存在键下。键可能是数、字符串或元组。

4.1 字典的用途

字典的名称指出了这种数据结构的用途。普通图书适合从头到尾的顺序阅读,如果你愿意,可以快速翻到任意一页,这有点像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是个字典就行了。

4.2 创建和使用字典

字典以类似于下面的方式表示:

phonebook = {'Alice': '2341', 'Beth': '9102', 'Cecil': '3258'}

字典由键及其对应的值组成,这种键-值对称为项(item)。在前面的示例中,键为名字,而值为电话号码。每个键与其值之间都用冒号(:)分隔,项之间用逗号分隔,而整个字典放在花括号内。空字典(没有任何项)用两个花括号表示,类似于:{}。

注:在字典(以及其他映射类型)中,键必须是独一无二的,而字典中的值无需如此。

4.2.1 函数dict

可使用函数dict从其他映射(如其他字典)或键-值对序列中创建字典。

>>> item = [('name', 'Gumby'), ('age', 42)]
>>> d = dict(item)
>>> d
{'name': 'Gumby', 'age': 42}
>>> d['name']
'Gumby'

还可使用关键字实参来调用这个函数,如下所示:

>>> d = dict(name='Gumby', age=42)
>>> d
{'name': 'Gumby', 'age': 42}

尽管这可能是函数dict最常见的用法,但可使用一个映射实参来调用它,这将创建一个字典,其中包含指定映射中的所有项。像函数list、tuple和str一样,如果调用这个函数时没有提供任何实参,将返回一个空字典。从映射创建字典时,如果该映射也是字典(毕竟字典是Python中唯一的内置映射类型),可不使用函数dict,而是使用字典方法copy,这将在本章后面介绍。

4.2.2基本的字典操作

字典的基本行为在很多方面都类似于序列:

  • 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 "<pyshell#1>", line 1, in <module>
    x[42] = 'Foobar'
IndexError: list assignment index out of range
>>> x = {}
>>> x[42] = 'Foobar'
>>> x
{42: 'Foobar'}

首先,我们尝试将字符串’Foobar’赋给一个空列表中索引为42的元素。这显然不可信,因为没有这种元素。要让这种操作可行,初始化x时,必须使用[None] * 43之类的代码,而不能使用[]。然而,接下来对字典所做的尝试完全可行。

4.2.3 将字符串格式设置功能用于字典

第三章介绍过,可使用字符串格式设置功能来设置值的格式,这些值是作为命名或非命名参数提供给方法format。在有些情况下,通过在字典中储存一系列命名的值,可让格式设置更容易些。例如,可在字典中包含各种信息,这样只需要在格式字符串中提取所需的信息即可。为此,必须使用format_map来指出你将通过一个映射来提供所需的信息。

>>> phonebook = {'Alice': '2341', 'Beth': '9102', 'Cecil': '3258'}
>>> "Cecil's phone number is {Cecil}.".format_map(phonebook)
"Cecil's phone number is 3258."

像这样使用字典时,可指定任意数量的转换说明符,条件是所有的字段名都是包含在字典中的键。在模板系统中,这种字符串格式设置方式很有用(下面的示例使用的是HTML(@_@;)? [不懂])。

>>> 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 字典方法

与其他内置类型一样,字典也有方法。字典的方法很有用,但其使用频率可能没有列表和字符串的方法那样高。可先学习,等需要使用特定方法时再回来探究原理。

1. clear

方法clear删除所有的字典项,这种操作是就地执行的(就像list.sort一样),因此什么都不返回(或者说返回None)。

>>> d = {}
>>> d['name'] = 'Gumby'
>>> d['age'] = 42
>>> d
{'name': 'Gumby', 'age': 42}
>>> returned_value = d.clear()
>>> d
{}
>>> print(returned_value)
None
##这是很有用的,下面是第一个场景:
>>> x = {}
>>> y = x
>>> x['key'] = 'value'
>>> y
{'key': 'value'}
>>> x = {}
>>> y
{'key': 'value'}
##下面是第二个场景:
>>> x = {}
>>> y
{'key': 'value'}
>>> x = {}
>>> y = x
>>> x['key'] = 'value'
>>> y
{'key': 'value'}
>>> x.clear()
>>> y
{}

在这两个场景中,x和y最初都指向同一个字典。在第一个场景中,我们通过将一个空字典赋给x来“清空”它。这对y没有任何影响,它依然指向原来的字典。这种行为可能正是我们想要的,但要删除原来字典的所有元素,必须使用clear。如果这样做,y将是空的,如第二个场景所示。

2. 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']}
##如我们所见,当替换副本中的值时,原件不受影响。然而,如果修改副本中的值(就地修改而不是替换),原件也将发生变换,因为原件指向的也是被修改的值。

为避免这一问题,一种办法是执行深复制,即同时复制值及其包含的所有值,等等。为此,可以使用模块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']}

3. fromkeys

方法fromkeys创建一个新字典,其中包含指定的键,且每个键对应的值都是None。

>>> {}.fromkeys(['name', 'age'])
{'name': None, 'age': None}

这个示例首先创建了一个空字典,再对其调用方法fromkeys来创建另一个字典,这显得有点多余。我们可以不这样做,而是直接对dict(dict是所有字典所属的类型)调用方法fromkeys。

>>> dict.fromkeys(['name', 'age'])
{'name': None, 'age': None}
##如果不想使用默认值None,可提供特定值。
>>> dict.fromkeys(['name', 'age'], '(unknow)')
{'name': '(unknow)', 'age': '(unknow)'}

4. get

方法get为访问字典项提供了宽松的环境。

##通常,如果我们试图访问字典中没有的项,将引发错误。如:
>>> d = {}
>>> print(d['name'])
Traceback (most recent call last):
  File "<pyshell#5>", line 1, in <module>
    print(d['name'])
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'

5. items

方法items返回一个包含所有字典项的列表,其中每个元素都为(key,value)的形式。字典项在列表中的排列顺序不确定。

>>> 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)])
#返回值属于一种名为字典视图的特殊类型。字典视图可用于迭代(第五章)。另外,还可确定其长度以及对其执行成员资格的审查:
>>> 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
#然而,如果你要将字典项复制到列表中,可自己动手做:
>>> list(d.items())
[('title', 'Python Web Site'), ('url', 'http://www.python.org'), ('spam', 0)]

6. keys

方法keys返回一个字典视图,其中包含指定字典中的键。

>>> d = {'title': 'Python Web Site', 'url': 'http://www.python.org', 'spam': 0}
>>> d.keys()
dict_keys(['title', 'url', 'spam'])

7. pop

方法pop可用于获取与指定键相关联的值,并将该键-值从对应字典中删除。

>>> d = {'x': 1, 'y': 2}
>>> d.pop('x')
1
>>> d
{'y': 2}

8. popitem

方法popitem类似于list.pop,但list.pop弹出列表中的最后一个元素,而popitem随机地弹出一个字典项,因为字典项的顺序是不确定的,没有“最后一个元素”的概念。如果我们要以高效的方式逐个删除并处理所有的字典项,这可能很有用,因为这样无需先获取键列表。

>>> d = {'title': 'Python Web Site', 'url': 'http://www.python.org', 'spam': 0}
>>> d.popitem()
('spam', 0)
>>> d
{'title': 'Python Web Site', 'url': 'http://www.python.org'}

虽然popitem类似于列表方法pop,但字典没有与append(它在列表末尾添加一个元素)对应的方法。这是因为字典是无序的,类似的方法毫无意义。

注:如果希望方法popitem以可预测的顺序弹出字典项,情参阅模块collections中的OrderedDict类。

9. 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。
>>> d = {}
>>> print(d.setdefault('name'))
None
>>> d
{'name': None}

提示:如果希望有用于整个字典的全局默认值,请参阅模块collections中的defaultdict类。

10. 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
{'title': 'Python Language Website', 'url': 'http://www.python.org', 'changed': 'Mar 14 22:09:15 MET 2016'}

对于通过参数提供的字典,将其添加到当前字典中。如果当前字典包含键相同的项,就替换它。

可像调用本章前面讨论的函数dict(类型构造函数)那样调用方法update。这意味着调用update时,可向它提供一个映射、一个由键-值对组成的序列(或其他可迭代对象)或关键字参数。

11. 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])
>>> d.keys()
dict_keys([1, 2, 3, 4])

4.3 小结

本章介绍了如下内容:

  • 映射:映射让你能够使用任何不可变的对象(最常用的是字符串和元组)来标识其元素。Python只有一种内置的映射类型,那就是字典。
  • 将字符串格式设置功能用于字典:要对字典执行字符串格式的设置操作,不能使用format和命名参数,而必须使用format_map。
  • 字典方法:字典有很多方法,这些方法的调用方式与列表和字符串的方法相同。

练习


 上一篇
Python学习第5章-条件、循环和其他语句 Python学习第5章-条件、循环和其他语句
第五章-条件、循环和其他语句我们已经见过几种语句(print语句、import语句和赋值语句),先来看看这些语句的其他一些用法,再深入探讨条件语句和循环语句。然后,我们将介绍列表推导,它们虽然是表达式,但工作原理几乎与条件语句和循环语句相同
2019-02-16
下一篇 
Python学习第3章-使用字符串 Python学习第3章-使用字符串
第三章-使用字符串本章将介绍如何使用字符串来设置其他值的格式(比如便于打印),并大致了解字符串方法可完成的重要任务,如拆分、合并和查找等。 3.1 字符串基本操作所有标准序列操作(索引、切片、乘法、成员资格审查、长度、最小值、最大值)都适用
2019-02-13
  目录