7.1 映射类型:字典

字典是Python语言中唯一的映射类型。映射类型对象里哈希值(键,key)和指向的对象(值。value)是一对多的关系。它们与Perl中的哈希类型(译者注:又称关联数组)相似,通常被认为是可变的哈希表。一个字典对象是可变的,它是一个容器类型,能存储任意个数的Python对象,其中也包括其他容器类型。字典类型和序列类型容器类(列表、元组)的区别是存储和访问数据的方式不同。序列类型只用数字类型的键(从序列的开始起按数值顺序索引)。映射类型可以用其他对象类型做键,一般最常见的是用字符串做键。和序列类型的键不同,映像类型的键直接或间接地和存储的数据值相关联。但因为在映射类型中,我们不再用“序列化排序”的键,所以映像类型中的数据是无序排列的。

显然,这并不影响我们使用映射类型,因为映射类型不要求用数字值做索引以从一个容器中获取对应的数据项。你可以用键直接“映射”到值,这就是为什么叫映射类型(“mapping type”)的原因。映射类型通常被称做哈希表,是因为字典对象就是哈希类型的。字典是Python中最强大的数据类型之一。

7.1 映射类型:字典 - 图1核心笔记:什么是哈希表?它们与字典的关系是什么?

序列类型用有序的数字键做索引将数据以数组的形式存储。一般索引值与所存储的数据毫无关系。还可以用另一种方式来存储数据:基于某种相关值,比如说一个字符串。我们在日常生活中一直这么做。把人们的电话号码按照他们的姓记录在电话簿上,按照时间在日历或约会簿上添加事件,等等。在这些例子中,你的键就是和数据项相关的值。哈希表是一种数据结构:它按照我们所要求的去工作。哈希表中存储的每一条数据,叫做一个值(value),是根据与它相关的一个被称作为键(key)的数据项进行存储的。键和值合在一起被称为“键-值对”(key-value pairs)。哈希表的算法是获取键,对键执行一个叫做哈希函数的操作,并根据计算的结果,选择在数据结构的某个地址中来存储你的值。任何一个值存储的地址皆取决于它的键。正因为这种随意性,哈希表中的值是没有顺序的。你拥有的是一个无序的数据集。

你所能获得的有序集合只能是字典中的键的集合或者值的集合。方法Keys()或values()返回一个列表,该列表是可排序的。你还可以用items()方法得到包含键、值对的元组的列表来排序。由于字典本身是哈希的,所以是无序的。

哈希表一般有很好的性能,因为用键查询相当快。

Python的字典是作为可变的哈希表实现的。如果你熟悉Perl的话,就可以发现字典与Perl中的“关系数组”或散列相似。

现在我们就来研究Python字典。一个字典条目的语法格式是键值。而且,多条字典条目被包含在大括号({})里。

7.1.1 如何创建字典和给字典赋值

创建字典只需要把字典赋值给一个变量,不管这个字典是否包含元素。

7.1 映射类型:字典 - 图2

7.1 映射类型:字典 - 图3

从Python 2.2版本起,可以用工厂方法dict()来创建字典。当我们详细讨论dict()的时候会看到更多的例子,现在来看一个小例子。

7.1 映射类型:字典 - 图4

从Python 2.3版本起,可以用一个很方便的内建方法fromkeys()来创建一个“默认”字典,字典中元素具有相同的值(如果没有给出,默认为None):

7.1 映射类型:字典 - 图5

7.1.2 如何访问字典中的值

要想遍历一个字典(一般用键),你只需要循环查看它的键,像这样:

7.1 映射类型:字典 - 图6

从Python 2.2开始,你可以不必再用keys()方法获取供循环使用的键值列表了。可以用迭代器来轻松地访问类序列对象(sequence-like objects),比如字典和文件。只需要用字典的名字就可以在for循环里遍历字典。

7.1 映射类型:字典 - 图7

要得到字典中某个元素的值,可以用你所熟悉的字典键加上中括号来得到。

7.1 映射类型:字典 - 图8

字典dict1是空的,字典dict2有两个数据元素。字典dict2的键是‘name’和‘port’,它们对应的值分别是‘earth’和80。就像你看到的,通过键‘name’可以得到字典中的元素的值。

如果我们想访问该字典中的一个数据元素,而它在这个字典中没有对应的键,将会产生一个错误:

7.1 映射类型:字典 - 图9

在这个例子中,我们试图获得字典中‘server’键所对应的值。你从上面的代码知道,‘server’这个键并不存在。检查一个字典中是否有某个键的最好方法是用字典的has_key()方法,或者另一种比较好的方法就是从2.2版本起用的,in或not in操作符。has_key()方法将会在未来的Python版本中弃用,所以用in或not in是最好的方法。

下面我们将介绍字典所有的方法。方法has_key()和in以及not in操作符都是布尔类型的。对于前两者而言,如果字典中有该键就返回真(True),否则返回假(False) (Python 2.3版本以前,没有布尔常量,为真时返回1,假时返回0)。

7.1 映射类型:字典 - 图10

一个字典中混用数字和字符串的例子:

7.1 映射类型:字典 - 图11

除了逐一地添加每个键-值对外,我们也可以给dict3整体赋值。

7.1 映射类型:字典 - 图12

如果事先已经知道所有的数据就可以用键-值对来创建一个字典(这是显而易见的)。通过字典dict3的示例说明你可以采用各种类型的数据作为字典的键。如果我们被问到是否可以改变某个字典值的键时,你可能会说“不”,对吗?

为什么在执行中字典中的键不允许被改变呢?你这样想就会明白:比方说,你创建了一个字典,字典中包含一个元素(一个键和一个值)。可能是由于某个变量的改变导致键发生了改变。这时候你如果用原来的键来取出字典里的数据,会得到KeyError(因为键的值已经改变了),现在你没办法从字典中获取该值了,因为键本身的值发生了变化。由于上面的原因,字典中的键必须是可哈希的,所以数字和字符串可以作为字典中的键,但是列表和其他字典不行(见7.5.2小节字典的键必须是可哈希的)。

7.1.3 如何更新字典

你可以通过以下几种方式对一个字典做修改:添加一个新数据项或新元素(即,一个键-值对);修改一个已存在的数据项;或删除一个已存在的数据项(下面有关于数据项删除操作的详细讲述)。

7.1 映射类型:字典 - 图13

如果字典中该键已经存在,则字典中该键对应的值将被新值替代。上面的print语句展示了另一种在字典中使用字符串格式符(%)的方法。用字典参数可以简化print语句,因为这样做你只需要用到一次该字典的名字,而不用在每个元素出现的时候都用元组参数表示。你也可以用内建方法update()将整个字典的内容添加到另一个字典。我们将在7.4节介绍此方法。

7.1.4 如何删除字典元素和字典

删除整个字典的操作不常见。通常,你删除字典中的单个元素或是清除整个字典的内容。但是,如果你真想“删除”一个字典,用del语句(介绍见小节3.5.5)。以下是删除字典和字典元素的例子。

7.1 映射类型:字典 - 图14

7.1 映射类型:字典 - 图15核心提示:避免使用内建对象名字作为变量的标识符

如果在Python2.3前,你已经开始使用Python,你可能用dict作为一个字典的标识符。但是,因为dict()现在已成为Python的类型和工厂方法,重载dict()会给你带来麻烦和潜在的bugs。编译器允许你做这样的重载,它认为你是聪明的,知道自己正在做什么!小心。请不要用dict、list、file、bool、str、input、len这样的内建类型为变量命名。