字典(1)

字典,这个东西你现在还用吗?随着网络的发展,用的人越来越少了。不少人习惯于在网上搜索,不仅有web版,乃至于已经有手机版的各种字典了。我在上小学的时候曾经用过一本小小的《新华字典》,记得是拾了不少废品,然后换钱,最终花费了1.01元人民币买的。

《新华字典》是中国第一部现代汉语字典。最早的名字叫《伍记小字典》,但未能编纂完成。自1953年,开始重编,其凡例完全采用《伍记小字典》。从1953年开始出版,经过反复修订,但是以1957年商务印书馆出版的《新华字典》作为第一版。原由新华辞书社编写,1956年并入中科院语言研究所(现中国社科院语言研究所)词典编辑室。新华字典由商务印书馆出版。历经几代上百名专家学者10余次大规模的修订,重印200多次。成为迄今为止世界出版史上最高发行量的字典。

这里讲到字典,不是为了回忆青葱岁月。而是提醒看官想想我们如何使用字典:先查索引(不管是拼音还是偏旁查字),然后通过索引找到相应内容。不用从头开始一页一页地找。

这种方法能够快捷的找到目标。

正是基于这种需要,python中有了一种叫做dictionary的数据类型,翻译过来就是“字典”,用dict表示。

假设一种需要,要存储城市和电话区号,苏州的区号是0512,唐山的是0315,北京的是011,上海的是012。用前面已经学习过的知识,可以这么来做:

>>> citys = ["suzhou", "tangshan", "beijing", "shanghai"]
>>> city_codes = ["0512", "0315", "011", "012"]

用一个列表来存储城市名称,然后用另外一个列表,一一对应地保存区号。假如要输出苏州的区号,可以这么做:

>>> print "{} : {}".format(citys[0], city_codes[0])
suzhou : 0512

请特别注意,我在city_codes中,表示区号的元素没有用整数型,而是使用了字符串类型,你知道为什么吗? 如果用整数,就是这样的。

>>> suzhou_code = 0512
>>> print suzhou_code
330

怎么会这样?原来在python中,如果按照上面那样做,0512并没有被认为是一个八进制的数,用print打印的时候,将它转换为了十进制输出。关于进制转换问题,看官可以网上搜索一下有关资料。此处不详述。一般是用几个内建函数实现:int(), bin(), oct(), hex()

这样来看,用两个列表分别来存储城市和区号,似乎能够解决问题。但是,这不是最好的选择,至少在python里面。因为python还提供了另外一种方案,那就是字典(dict)。

创建dict

方法1:

创建一个空的dict,这个空dict,可以在以后向里面加东西用。

>>> mydict = {}
>>> mydict
{}

不要小看“空”,“色即是空,空即是色”,在编程中,“空”是很重要。一般带“空”字的人都很有名,比如孙悟空,哦。好像他应该是猴、或者是神。举一个人的名字,带“空”字,你懂得。

创建有内容的dict。

>>> person = {"name":"qiwsir","site":"qiwsir.github.io","language":"python"}
>>> person
{'name': 'qiwsir', 'language': 'python', 'site': 'qiwsir.github.io'}

"name":"qiwsir",有一个优雅的名字:键值对。前面的name叫做键(key),后面的qiwsir是前面的键所对应的值(value)。在一个dict中,键是唯一的,不能重复。值则是对应于键,值可以重复。键值之间用(:)英文的冒号,每一对键值之间用英文的逗号(,)隔开。

>>> person['name2'] = "qiwsir"    #这是一种向dict中增加键值对的方法
>>> person
{'name2': 'qiwsir', 'name': 'qiwsir', 'language': 'python', 'site': 'qiwsir.github.io'}

用这样的方法可以向一个dict类型的数据中增加“键值对”,也可以说是增加数值。那么,增加了值之后,那个字典还是原来的吗?也就是也要同样探讨一下,字典是否能原地修改?(列表可以,所以列表是可变的;字符串和元组都不行,所以它们是不可变的。)

>>> ad = {}
>>> id(ad)
3072770636L
>>> ad["name"] = "qiwsir"
>>> ad
{'name': 'qiwsir'}
>>> id(ad)
3072770636L

实验表明,字典可以原地修改,即它是可变的。

方法2:

利用元组在建构字典,方法如下:

>>> name = (["first","Google"],["second","Yahoo"])      
>>> website = dict(name)
>>> website
{'second': 'Yahoo', 'first': 'Google'}

或者用这样的方法:

>>> ad = dict(name = "qiwsir", age = 42)
>>> ad
{'age': 42, 'name': 'qiwsir'}

方法3:

这个方法,跟上面的不同在于使用fromkeys

>>> website = {}.fromkeys(("third","forth"),"facebook")
>>> website
{'forth': 'facebook', 'third': 'facebook'}

需要提醒的是,这种方法是重新建立一个dict。

需要提醒注意的是,在字典中的“键”,必须是不可变的数据类型;“值”可以是任意数据类型。

>>> dd = {(1,2):1}
>>> dd
{(1, 2): 1}
>>> dd = {[1,2]:1}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

访问dict的值

dict数据类型是以键值对的形式存储数据的,所以,只要知道键,就能得到值。这本质上就是一种映射关系。

映射,就好比“物体”和“影子”的关系,“形影相吊”,两者之间是映射关系。此外,映射也是一个严格数学概念:A是非空集合,A到B的映射是指:A中每个元素都对应到B中的某个元素。

既然是映射,就可以通过字典的“键”找到相应的“值”。

>>> person
{'name2': 'qiwsir', 'name': 'qiwsir', 'language': 'python', 'site': 'qiwsir.github.io'}
>>> person['name']
'qiwsir'
>>> person['language']
'python'

如同前面所讲,通过“键”能够增加dict中的“值”,通过“键”能够改变dict中的“值”,通过“键”也能够访问dict中的“值”。

本节开头那个城市和区号的关系,也可以用字典来存储和读取。

>>> city_code = {"suzhou":"0512", "tangshan":"0315", "beijing":"011", "shanghai":"012"}
>>> print city_code["suzhou"]
0512

既然dict是键值对的映射,就不用考虑所谓“排序”问题了,只要通过键就能找到值,至于这个键值对位置在哪里就不用考虑了。比如,刚才建立的city_code

>>> city_code
{'suzhou': '0512', 'beijing': '011', 'shanghai': '012', 'tangshan': '0315'}

虽然这里显示的和刚刚赋值的时候顺序有别,但是不影响读取其中的值。

在list中,得到值是用索引的方法。那么在字典中有索引吗?当然没有,因为它没有顺序,哪里来的索引呢?所以,在字典中就不要什么索引和切片了。

dict中的这类以键值对的映射方式存储数据,是一种非常高效的方法,比如要读取值得时候,如果用列表,python需要从头开始读,直到找到指定的那个索引值。但是,在dict中是通过“键”来得到值。要高效得多。 正是这个特点,键值对这样的形式可以用来存储大规模的数据,因为检索快捷。规模越大越明显。所以,mongdb这种非关系型数据库在大数据方面比较流行了。

基本操作

字典虽然跟列表有很大的区别,但是依然有不少类似的地方。它的基本操作:

  • len(d),返回字典(d)中的键值对的数量
  • d[key],返回字典(d)中的键(key)的值
  • d[key]=value,将值(value)赋给字典(d)中的键(key)
  • del d[key],删除字典(d)的键(key)项(将该键值对删除)
  • key in d,检查字典(d)中是否含有键为key的项

下面依次进行演示。

>>> city_code
{'suzhou': '0512', 'beijing': '011', 'shanghai': '012', 'tangshan': '0315'}
>>> len(city_code)
4

以city_code为操作对象,len(city_code)的值是4,表明有四组键值对,也可以说是四项。

>>> city_code["nanjing"] = "025"
>>> city_code
{'suzhou': '0512', 'beijing': '011', 'shanghai': '012', 'tangshan': '0315', 'nanjing': '025'}

向其中增加一项

>>> city_code["beijing"] = "010"
>>> city_code
{'suzhou': '0512', 'beijing': '010', 'shanghai': '012', 'tangshan': '0315', 'nanjing': '025'}

突然发现北京的区号写错了。可以这样修改。这进一步说明字典的值是可变的。

>>> city_code["shanghai"]
'012'
>>> del city_code["shanghai"]

通过city_code["shanghai"]能够查看到该键(key)所对应的值(value),结果发现也错了。干脆删除,用del,将那一项都删掉。

>>> city_code["shanghai"]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'shanghai'
>>> "shanghai" in city_code
False

因为键是"shanghai"的那个键值对项已经删除了,随意不能找到,用in来看看,返回的是False

>>> city_code
{'suzhou': '0512', 'beijing': '010', 'tangshan': '0315', 'nanjing': '025'}

真的删除了哦。没有了。

字符串格式化输出

这是一个前面已经探讨过的话题,请参看《字符串(4)》,这里再次提到,就是因为用字典也可以实现格式化字符串的目的。虽然在《字符串(4)》那节中已经有了简单演示,但是我还是愿意重复一下。

>>> city_code = {"suzhou":"0512", "tangshan":"0315", "hangzhou":"0571"}
>>> " Suzhou is a beautiful city, its area code is %(suzhou)s" % city_code
' Suzhou is a beautiful city, its area code is 0512'

这种写法是非常简洁,而且很有意思的。有人说它简直是酷。

其实,更酷还是下面的——模板

在做网页开发的时候,通常要用到模板,也就是你只需要写好HTML代码,然后将某些部位空出来,等着python后台提供相应的数据即可。当然,下面所演示的是玩具代码,基本没有什么使用价值,因为在真实的网站开发中,这样的姿势很少用上。但是,它绝非花拳绣腿,而是你能够明了其本质,至少了解到一种格式化方法的应用。

>>> temp = "<html><head><title>%(lang)s<title><body><p>My name is %(name)s.</p></body></head></html>"
>>> my = {"name":"qiwsir", "lang":"python"}
>>> temp % my
'<html><head><title>python<title><body><p>My name is qiwsir.</p></body></head></html>'

temp就是所谓的模板,在双引号所包裹的实质上是一段HTML代码。然后在dict中写好一些数据,按照模板的要求在相应位置显示对应的数据。

是不是一个很有意思的屠龙之技?

什么是HTML? 下面是在《维基百科》上抄录的:

超文本标记语言(英文:HyperText Markup Language,HTML)是为「网页创建和其它可在网页浏览器中看到的信息」设计的一种标记语言。HTML被用来结构化信息——例如标题、段落和列表等等,也可用来在一定程度上描述文档的外观和语义。1982年由蒂姆·伯纳斯-李创建,由IETF用简化的SGML(标准通用标记语言)语法进行进一步发展的HTML,后来成为国际标准,由万维网联盟(W3C)维护。

HTML经过发展,现在已经到HTML5了。现在的HTML设计,更强调“响应式”设计,就是能够兼顾PC、手机和各种PAD的不同尺寸的显示器浏览。如果要开发一个网站,一定要做到“响应式”设计,否则就只能在PC上看,在手机上看就不得不左右移动。

知识

什么是关联数组?以下解释来自维基百科

在计算机科学中,关联数组(英语:Associative Array),又称映射(Map)、字典(Dictionary)是一个抽象的数据结构,它包含着类似于(键,值)的有序对。一个关联数组中的有序对可以重复(如C++中的multimap)也可以不重复(如C++中的map)。

这种数据结构包含以下几种常见的操作:

1.向关联数组添加配对 2.从关联数组内删除配对 3.修改关联数组内的配对 4.根据已知的键寻找配对

字典问题是设计一种能够具备关联数组特性的数据结构。解决字典问题的常用方法,是利用散列表,但有些情况下,也可以直接使用有地址的数组,或二叉树,和其他结构。

许多程序设计语言内置基本的数据类型,提供对关联数组的支持。而Content-addressable memory则是硬件层面上实现对关联数组的支持。

什么是哈希表?关于哈希表的叙述比较多,这里仅仅截取了概念描述,更多的可以到维基百科上阅读

散列表(Hash table,也叫哈希表),是根据关键字(Key value)而直接访问在内存存储位置的数据结构。也就是说,它通过把键值通过一个函数的计算,映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。


总目录   |   上节:元组   |   下节:字典(2)

如有疑问,请到Python论坛提问