一种可通过名称来访问其各个值的数据结构。这种数据结构称为映射(mapping)。字典是Python中唯一的内置映射类型,其中的值不按顺序排列,而是存储在键下。键可能是数、字符串或元组。
26.1字典的用途
假设有如下名单:
>>> names = ['Alice', 'Beth', 'Cecil', 'Dee', 'Earl']
如果要存储这些人的电话号码,该如何办呢?一种办法是再创建一个列表。假设只存储四位的分机号,这个列表将类似于:
>>> numbers = ['2341', '9102', '3158', '0142', '5551']
创建这些列表后,就可像下面这样查找Cecil的电话号码:
>>> numbers[names.index('Cecil')]
>>> '3158'
这不太实用。实际上,你希望能够像下面这样做:
>>> phonebook['Cecil']
>>> '3158'
如何达成这个目标呢?把phonebook定义为字典。
26.2 创建和使用字典
字典类似于下面的表示:
phonebook = {'Alice': '2341', 'Beth': '9102', 'Cecil': '3258'}
字典由键及其相应的值组成,这种键值对称为项(item)。在前面的示例中,键为名字,而值为电话号码。每个键与其值之间都用冒号(:)分隔,项之间用逗号分隔,而整个字典放在花括号内。空字典(没有任何项)用两个花括号表示,类似于下面这样:{}。
把数据放入dict的方法,除了初始化时指定外,还可以通过key放入:
>>> phonebook['Alice'] = '2341'
>>> phonebook['Alice']
'2341'
由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:
>>> phonebook['Alice'] = '4321'
>>> d['Jack']
'4321'
>>> phonebook['Alice'] = '4312'
>>> d['Jack']
'4312'
如果key不存在,dict就会报错:
>>> phonebook['Thomas']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'Thomas'
26.2.1 dict函数
>>> persons = [('name','Gumby'), ('age',42)]
>>> d = dict(persons)
>>> d
{
'name': 'Gumby', 'age': 42}
>>> d = dict(name = 'Gumby', age = 42)
>>> d
{
'name': 'Gumby', 'age': 42}
26.2.2 基本的字典操作
字典与列表的一些相同行为
len(d)
返回字典d包含的项数d[k]
返回与键k相关联的值d[k] = v
将值v关联到键k。del d[k]
删除键为k的项k in d
检查字典d是否包含键为k的项。
26.3 字典和列表的不同
请务必注意,dict内部存放的顺序和key放入的顺序是没有关系的。
- **键的类型:**字典中的键可以是任何不可变的类型
- **自动添加:**字典中原来没有的键,也可以给它赋值并添加到字典创建新项。列表中没有的元素不可以赋值,除非使用append方法。
>>> 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'}
- **成员资格:**d是字典,l是列表,k in d 查找的是键,v in l查找的是值。
和list比较,dict有以下几个特点:
- 查找和插入的速度极快,不会随着key的增加而变慢;
- 需要占用大量的内存,内存浪费多。
而list相反:
- 查找和插入的时间随着元素的增加而增加;
- 占用空间小,浪费内存很少。
所以,dict是用空间来换取时间的一种方法。
dict用在需要高速查找的很多地方,在Python代码中几乎无处不在,正确使用dict非常重要,需要牢记的一条就是dict的key必须是不可变对象。
这是因为dict根据key来计算value的存储位置,如果每次计算相同的key得出的结果不同,那dict内部就完全混乱了。这个通过key计算位置的算法称为哈希算法(Hash)。
要保证hash的正确性,作为key的对象就不能变。在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key:
>>> key = [1, 2, 3]
>>> d[key] = 'a list'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
26.4 遍历字典
一个Python字典可能包含几个键—值对,也可能包含数百万个键—值对。鉴于字典可能包含大量的数据,Python支持对字典遍历。遍历字典的方式:可遍历字典的所有键—值对、键或值。
26.4.1 遍历所有的键—值对
先来看一个新字典,它用于存储有关网站用户的信息。下面的字典存储一名用户的用户名、名和姓:
user_0 = {
'username': 'efermi',
'first': 'enrico',
'last': 'fermi',
}
如果要显示该字典中的所有信息,该怎么办呢?可以使用一个for循环来遍历这个字典:
user.py
user_0 = {
'username': 'efermi',
'first': 'enrico',
'last': 'fermi',
}
# print(user_0.items())
for key, value in user_0.items():
print("\nKey: " + key)
print("Value: " + value)
要编写遍历字典的for循环,可声明两个变量,用于存储键—值对中的键和值。对于这两个变量,可使用任何名称。下面的代码使用了简单的变量名:
for k, v in user_0.items()
for语句的第二部分包含字典名和方法items()
,它返回一个键—值对列表。
print(user_0.items())
dict_items([('username', 'efermi'), ('first', 'enrico'), ('last', 'fermi')])
还记得:width, height = (640, 480)
这种赋值方式吗?
for循环依次将每个键—值对存储到指定的两个变量中。前面的示例中,使用这两个变量来打印每个键及其相关联的值。第一条print语句中的\n
确保在输出每个键—值对前都插入一个空行:
Key: last
Value: fermi
Key: first
Value: enrico
Key: username
Value: efermi
注意,遍历字典时,键—值对的返回顺序也与存储顺序不同。Python不关心键—值对的存储顺序,而只跟踪键和值之间的关联关系。
示例favorite_languages.py中,其中的键都是人名,值都是语言,在循环中使用变量
name和language,而不是key和value,这让人更容易明白循环的作用:
favorite_languages.py
favorite_languages = {
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python',
}
for name, language in favorite_languages.items():
print(name.title() + "'s favorite language is " + language.title() + ".")
仅使用几行代码,我们就将全部调查结果显示出来了:
Jen’s favorite language is Python.
Sarah’s favorite language is C.
Phil’s favorite language is Python.
Edward’s favorite language is Ruby.
即便字典存储的是上千乃至上百万人的调查结果,这种循环也管用。
26.4.2 遍历字典中的所有键
在不需要使用字典中的值时,方法keys()很有用。下面来遍历字典favorite_languages,并
将每个被调查者的名字都打印出来:
favorite_languages = {
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python',
}
# print(favorite_languages.keys())
for name in favorite_languages.keys():
print(name.title())
Python用key()
方法提取字典favorite_languages中的所有键,并依次将它们存储到变量name中。输出了每个被调查者的名字:
Jen
Sarah
Phil
Edward
遍历字典时,会默认遍历所有的键,因此,如果将上述代码中的for name in favorite_languages.keys():
替换为for name in favorite_languages:
,输出将不变。 如果显式地使用方法keys()可让代码更容易理解,你可以选择这样做,但如果你愿意,也可省略它。
在循环中,可使用当前键来访问与之相关联的值。下面来打印两条消息,指出两位朋友喜欢的语言。在名字为指定朋友的名字时,打印其喜欢的语言:
favorite_languages = {
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python',
}
friends = ['phil', 'sarah']
for name in favorite_languages.keys():
print(name.title())
if name in friends:
print(" Hi " + name.title() + ", Your favorite language is " + favorite_languages[name] + "!")
我们创建了一个friends
列表,其中包含我们要指出其喜欢的语言的朋友。
在循环中,我们打印每个人的名字,并检查当前的名字是否在列表friends
中。如果在列表中,就打印一句问候语,我们使用了字典名,并将变量name的当前值作为键。:
Edward
Phil
Hi Phil, I see your favorite language is Python!
Sarah
Hi Sarah, I see your favorite language is C!
Jen
你还可以使用keys()确定某个人是否接受了调查。下面的代码确定Erin是否接受了调查:
favorite_languages = {
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python',
}
if 'erin' not in favorite_languages.keys():
print("Erin, please take our poll!")
方法keys()
并非只能用于遍历;实际上,它返回一个列表,其中包含字典中的所有键,因此代码行只是核实’erin’是否包含在这个列表中。由于她并不包含在这个列表中,因此打印一条消息,邀请她参加调查:
Erin, please take our poll!
26.4.3 遍历字典中的所有值
如果只关心字典包含的值,可使用方法values()
,它返回一个值列表,而不包含任何键。例如,如果我们想获得一个这样的列表,即其中只包含被调查者选择的各种语言,而不包含被调查者的名字,可以这样做:
favorite_languages = {
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python',
}
print("The following languages have been mentioned:")
for language in favorite_languages.values():
print(language)
for语句提取字典中的每个值,并依次存储到变量language中。打印这些值:
The following languages have been mentioned:
Python
C
Python
Ruby
这种做法提取字典中所有的值,而没有考虑是否重复。涉及的值很少时,这也许不是问题,但如果被调查者很多,最终的列表可能包含大量的重复项。为剔除重复项,可使用集合(set)。
集合类似于列表,但每个元素都必须是独一无二的:
favorite_languages = {
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python',
}
print("The following languages have been mentioned:")
for language in set(favorite_languages.values()):
print(language)
通过对包含重复元素的列表调用set()
,可让Python找出列表中独一无二的元素,并使用这些元素来创建一个集合。。
结果是一个不重复的列表,其中列出了被调查者提及的所有语言:
The following languages have been mentioned:
Python
C
Ruby
随着你更深入地学习Python,经常会发现它内置的功能可帮助你以希望的方式处理数据。
26.5 set
set
和dict
类似,是一组key
的集合,但不存储value
。由于key
不能重复,所以,在set中,没有重复的key。
要创建一个set,需要提供一个list作为输入集合:
>>> s = set([1, 2, 3])
>>> s
{1, 2, 3}
注意,传入的参数[1, 2, 3]
是一个list,而显示的{1, 2, 3}
只是告诉你这个set内部有1,2,3这3个元素,显示的顺序也不表示set是有序的。。
重复元素在set中自动被过滤:
>>> s = set([1, 1, 2, 2, 3, 3])
>>> s
{1, 2, 3}
通过add(key)
方法可以添加元素到set中,可以重复添加,但不会有效果:
>>> s.add(4)
>>> s
{1, 2, 3, 4}
>>> s.add(4)
>>> s
{1, 2, 3, 4}
通过remove(key)
方法可以删除元素:
>>> s.remove(4)
>>> s
{1, 2, 3}
set可以看成数学意义上的无序和无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作:
>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s1 & s2
{2, 3}
>>> s1 | s2
{1, 2, 3, 4}
set和dict的唯一区别仅在于没有存储对应的value,但是,set的原理和dict一样,所以,同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证set内部“不会有重复元素”。试试把list放入set,看看是否会报错。
26.6 再谈不可变对象
上面我们讲了,str是不变对象,而list是可变对象。
对于可变对象,比如list,对list进行操作,list内部的内容是会变化的,比如:
>>> a = ['c', 'b', 'a']
>>> a.sort()
>>> a
['a', 'b', 'c']
而对于不可变对象,比如str,对str进行操作呢:
>>> a = 'abc'
>>> a.replace('a', 'A')
'Abc'
>>> a
'abc'
虽然字符串有个replace()
方法,也确实变出了'Abc'
,但变量a
最后仍是'abc'
,应该怎么理解呢?
我们先把代码改成下面这样:
>>> a = 'abc'
>>> b = a.replace('a', 'A')
>>> b
'Abc'
>>> a
'abc'
要始终牢记的是,a
是变量,而'abc'
才是字符串对象!有些时候,我们经常说,对象a
的内容是'abc'
,但其实是,a
本身是一个变量,它指向的对象的内容才是'abc'
:
┌───┐ ┌───────┐
│ a │─────────────────>│ 'abc' │
└───┘ └───────┘
当我们调用a.replace('a', 'A')
时,实际上调用方法replace
是作用在字符串对象'abc'
上的,而这个方法虽然名字叫replace
,但却没有改变字符串'abc'
的内容。相反,replace
方法创建了一个新字符串'Abc'
并返回,如果我们用变量b
指向该新字符串,就容易理解了,变量a
仍指向原有的字符串'abc'
,但变量b
却指向新字符串'Abc'
了:
┌───┐ ┌───────┐
│ a │─────────────────>│ 'abc' │
└───┘ └───────┘
┌───┐ ┌───────┐
│ b │─────────────────>│ 'Abc' │
└───┘ └───────┘
所以,对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的。
26.7 小结
dict在Python中非常有用,key必须是不可变对象,最常用的key是字符串。
tuple虽然是不变对象,但(1, 2, 3)
可以作为dict或set的key,但(1,[2,3])
不可以,试着解释结果。