《Python基础教程》第4章 当索引行不通时

需要将一系列值组合成数据结构并通过编号来访问各个值时,列表很有用。本章介绍一种可通过名称来访问其各个值的数据结构。这种数据结构称为映射( mapping)。字典是Python中唯一的内置映射类型,其中的值不按顺序排列,而是存储在键下。键可能是数、字符串或元组。
4.1 字典的用途
字典的名称指出了这种数据结构的用途。普通图书适合按从头到尾的顺序阅读,如果你愿意,
可快速翻到任何一页,这有点像Python中的列表。字典(日常生活中的字典和Python字典)旨在
让你能够轻松地找到特定的单词(键),以获悉其定义(值)。
在很多情况下,使用字典都比使用列表更合适。下面是Python字典的一些用途:
 表示棋盘的状态,其中每个键都是由坐标组成的元组;
 存储文件修改时间,其中的键为文件名;
 数字电话/地址簿。
假设有如下名单:
>>> names = ['Alice', 'Beth', 'Cecil', 'Dee-Dee', 'Earl']

如果要创建一个小型数据库,在其中存储这些人的电话号码,该如何办呢?一种办法是再创
建一个列表。假设只存储四位的分机号,这个列表将类似于:

 这可行,但不太实用。实际上,你希望能够像下面这样做:
>>> phonebook['Cecil']
'3158'
如何达成这个目标呢?只要phonebook是个字典就行了。
4.2 创建和使用字典
字典以类似于下面的方式表示:
phonebook = {'Alice': '2341', 'Beth': '9102', 'Cecil': '3258'}
字典由键及其相应的值组成,这种键值对称为项( item)。在前面的示例中,键为名字,而
值为电话号码。每个键与其值之间都用冒号( :)分隔,项之间用逗号分隔,而整个字典放在花
括号内。空字典(没有任何项)用两个花括号表示,类似于下面这样: {}。

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

 尽管这可能是函数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是一个列表)查找的是值而不是索引。这看似不太一致,但你习惯后就会觉得相当自
然。毕竟如果字典包含指定的键,检查相应的值就很容易。

 前述第一点(键可以是任何不可变的类型)是字典的主要优点。第二点也很重要,下面的示
例说明了这种差别:

 首先,我尝试将字符串'Foobar'赋给一个空列表中索引为42的元素。这显然不可能,因为
没有这样的元素。要让这种操作可行,初始化x时,必须使用[None] * 43之类的代码,而不能使
用[]。然而,接下来的尝试完全可行。这次我将'Foobar'赋给一个空字典的键42;如你所见,这
样做一点问题都没有:在这个字典中添加了一个新项,我得逞了。
代码清单4-1列出了创建电话簿数据库的代码。
代码清单4-1 字典示例
 

# 一个简单的数据库
# 一个将人名用作键的字典。每个人都用一个字典表示,
# 字典包含键'phone'和'addr',它们分别与电话号码和地址相关联
people = {
    'Alice':{
        'phone':'2341',
        'addr':'Foo drive 23'
    },
    'Beth':{
        'phone':'9102',
        'addr':'Bar street 42'
    },
    'Cecil':{
        'phone':'3158',
        'addr':'Baz avenue 90'
    }
}

# 电话号码和地址的描述性标签,供打印输出时使用
labels = {
    'phone': 'phone number',
    'addr': 'address'
}

name = input('Name:')

# 要查找电话号码还是地址?
request = input('Phone number(p) or address (a)?')

# 使用正确的键:
if request == 'p': key = 'phone'
if request == 'a': key = 'addr'
# 仅当名字是字典包含的键时才打印信息:
if name in people: print("{}'s {} is {}.".format(name, labels[key], people[name][key]))

运行结果:

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

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

 4.2.4 字典方法
与其他内置类型一样,字典也有方法。字典的方法很有用,但其使用频率可能没有列表和字符串的方法那样高。你可大致浏览一下本节,了解字典提供了哪些方法,等需要使用特定方法时再回过头来详细研究其工作原理。
1. clear
方法clear删除所有的字典项,这种操作是就地执行的(就像list.sort一样),因此什么都不返回(或者说返回None)。

 这为何很有用呢?我们来看两个场景。下面是第一个场景:

 下面是第二个场景:

 在这两个场景中, x和y最初都指向同一个字典。在第一个场景中,我通过将一个空字典赋
给x来“清空”它。这对y没有任何影响,它依然指向原来的字典。这种行为可能正是你想要的,
但要删除原来字典的所有元素,必须使用clear。如果这样做, y也将是空的,如第二个场景所示。
2. copy
方法copy返回一个新字典,其包含的键-值对与原来的字典相同(这个方法执行的是浅复制
因为值本身是原件,而非副本)。

 如你所见,当替换副本中的值时,原件不受影响。然而,如果修改副本中的值(就地修改而
不是替换),原件也将发生变化,因为原件指向的也是被修改的值(如这个示例中的'machines'
列表所示)。
为避免这种问题,一种办法是执行深复制,即同时复制值及其包含的所有值,等等。为此,
可使用模块copy中的函数deepcopy。

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

 这个示例首先创建了一个空字典,再对其调用方法fromkeys来创建另一个字典,这显得有点
多余。你可以不这样做,而是直接对dict(前面说过, dict是所有字典所属的类型。类和类型将
在第7章详细讨论)调用方法fromkeys。

 4. get
方法get为访问字典项提供了宽松的环境。通常,如果你试图访问字典中没有的项,将引发
错误。

 如你所见,使用get来访问不存在的键时,没有引发异常,而是返回None。你可指定“默认”
值,这样将返回你指定的值而不是None。

 代码清单4-2是代码清单4-1所示程序的修改版本,它使用了方法get来访问“数据库”条目。
代码清单4-2 字典方法示例
 

# 一个使用get()的简单数据库
# 在这里插入代码清单4-1中的数据库(字典people)
people = {
    'Alice':{
        'phone':'2341',
        'addr':'Foo drive 23'
    },
    'Beth':{
        'phone':'9102',
        'addr':'Bar street 42'
    },
    'Cecil':{
        'phone':'3158',
        'addr':'Baz avenue 90'
    }
}

labels = {
    'phone': 'phone number',
    'addr': 'address'
}

name = input('Name:')
# 要查找电话号码还是地址?
request = input('Phone number(p) or address(a)?')
# 使用正确的键:
key = request  # 如果request既不是'p'也不是'a'
if request == 'p': key = 'phone'
if request == 'a': key = 'addr'

# 使用get提供默认值
person = people.get(name, {})
label = labels.get(key, key)
result = person.get(key, 'not availabel')

print("{}'s {} is {}.".format(name, label, result))

下面是这个程序的运行情况。注意到get提高了灵活性,让程序在用户输入的值出乎意料时
也能妥善处理。

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

 返回值属于一种名为字典视图的特殊类型。字典视图可用于迭代(迭代将在第5章详细介绍)。
另外,你还可确定其长度以及对其执行成员资格检查。

 视图的一个优点是不复制,它们始终是底层字典的反映,即便你修改了底层字典亦如此。

 然而,如果你要将字典项复制到列表中(在较旧的Python版本中,方法items就是这样做的),
可自己动手做。

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

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

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

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

 9. setdefault
方法setdefault有点像get,因为它也获取与指定键相关联的值,但除此之外, setdefault还在字典不包含指定的键时,在字典中添加指定的键-值对。

 如你所见,指定的键不存在时, setdefault返回指定的值并相应地更新字典。如果指定的键
存在,就返回其值,并保持字典不变。与get一样,值是可选的;如果没有指定,默认为None。

 

 10. update
方法update使用一个字典中的项来更新另一个字典。

 对于通过参数提供的字典,将其项添加到当前字典中。如果当前字典包含键相同的项,就替
换它。
可像调用本章前面讨论的函数dict(类型构造函数)那样调用方法update。这意味着调用update时,可向它提供一个映射、一个由键值对组成的序列(或其他可迭代对象)或关键字参数。
11. values
方法values返回一个由字典中的值组成的字典视图。不同于方法keys,方法values返回的视
图可能包含重复的值。

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

 4.3.2 预告
至此,你对Python基本数据类型以及如何使用它们来创建表达式有了深入的认识。你可能还
记得,第1章提到计算机程序还包含另一个要素——语句。下一章将详细讨论。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值