[TOC]
### dict 内置字典
Python内置了字典:`dict`的支持,`dict`全称`dictionary`,在其他语言中也称为`map`,使用键-值(key-value)存储,具有极快的查找速度。
#### list 查询
举个例子,假设要根据同学的名字查找对应的成绩,如果用`list`实现,需要两个`list`:
~~~
names = ['Michael', 'Bob', 'Tracy']
scores = [95, 75, 85]
~~~
给定一个名字,要查找对应的成绩,就先要在`names`中找到对应的位置,再从`scores`取出对应的成绩,`list`越长,耗时越长。
#### dict 查询
如果用`dict`实现,只需要一个“名字”-“成绩”的对照表,直接根据名字查找成绩,无论这个表有多大,查找速度都不会变慢。用Python写一个`dict`如下:
~~~
>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
>>> d['Michael']
95
~~~
#### 两种方法的对比
为什么`dict`查找速度这么快?因为`dict`的实现原理和查字典是一样的。假设字典包含了1万个汉字,我们要查某一个字。
- 一个办法是把字典从第一页往后翻,直到找到我们想要的字为止,这种方法就是在`list`中查找元素的方法,`list`越大,查找越慢。
- 第二种方法是先在字典的索引表里(比如部首表)查这个字对应的页码,然后直接翻到该页,找到这个字。无论找哪个字,这种查找速度都非常快,不会随着字典大小的增加而变慢。
`dict`就是第二种实现方式,给定一个名字,比如`'Michael'`,`dict`在内部就可以直接计算出`Michael`对应的存放成绩的`“页码”`,也就是`95`这个数字存放的内存地址,直接取出来,所以速度非常快。
#### 数据放入 dict 中
你可以猜到,这种`key-value`存储方式,在放进去的时候,必须根据`key`算出`value`的存放位置,这样,取的时候才能根据key直接拿到value。
把数据放入dict的方法,除了初始化时指定外,还可以通过key放入:
~~~
>>> d['Adam'] = 67
>>> d['Adam']
67
~~~
##### 存入数据注意
由于一个`key`只能对应一个`value`,所以,多次对一个`key`放入`value`,后面的值会把前面的值冲掉:
~~~
>>> d['Jack'] = 90
>>> d['Jack']
90
>>> d['Jack'] = 88
>>> d['Jack']
88
~~~
如果key不存在,dict就会报错:
~~~
>>> d['Thomas']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'Thomas'
~~~
##### 判断 key 是否存在
要避免key不存在的错误,有两种办法,
- 一是通过`in`判断`key`是否存在:
~~~
>>> 'Thomas' in d
False
~~~
- 二是通过`dict`提供的`get`方法,如果`key`不存在,可以返回`None`,或者自己指定的`value`:
~~~
>>> d.get('Thomas')
>>> d.get('Thomas', -1)
-1
~~~
注意:返回`None`的时候Python的交互式命令行不显示结果。
##### 删除 key
要删除一个`key`,用`pop(key)`方法,对应的`value`也会从`dict`中删除:
~~~
>>> d.pop('Bob')
75
>>> d
{'Michael': 95, 'Tracy': 85}
~~~
请务必注意,`dict`内部存放的顺序和`key`放入的顺序是没有关系的。
#### dict 与 list 比较
和`list`比较,`dict`有以下几个特点:
- 查找和插入的速度极快,不会随着key的增加而变慢;
- 需要占用大量的内存,内存浪费多。
而list相反:
- 查找和插入的时间随着元素的增加而增加;
- 占用空间小,浪费内存很少。
所以,dict是用空间来换取时间的一种方法。
#### 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'
~~~
### set
#### 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`可以看成数学意义上的无序和无重复元素的集合,因此,两个`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`,看看是否会报错。
#### 再议不可变对象
上面我们讲了,`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'
~~~
>[info]要始终牢记的是,`a`是变量,而`'abc'`才是字符串对象!有些时候,我们经常说,对象`a`的内容是`'abc'`,但其实是指,`a`本身是一个变量,它指向的对象的内容才是`'abc'`:
![image](http://www.liaoxuefeng.com/files/attachments/001389580505217f87b492b060b4b0ea60c8e5e70a1b53c000/0)
- 当我们调用`a.replace('a', 'A')`时,实际上调用方法`replace`是作用在字符串对象`'abc'`上的,而这个方法虽然名字叫`replace`,但却没有改变字符串`'abc'`的内容。相反,`replace`方法创建了一个新字符串`'Abc'`并返回,如果我们用变量`b`指向该新字符串,就容易理解了,变量`a`仍指向原有的字符串`'abc'`,但变量b却指向新字符串`'Abc'`了:
![image](http://www.liaoxuefeng.com/files/attachments/001389580620829061e426d429640ddb1d17174a82a7244000/0)
所以,对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的。
### 小结
#### dict
使用`key-value`存储结构的`dict`在Python中非常有用,选择不可变对象作为`key`很重要,最常用的key是字符串。
#### tuple虽然是不变对象,但试试把(1, 2, 3)和(1, [2, 3])放入dict或set中,并解释结果。
- Python教程
- Python简介
- 安装Python
- Python解释器
- 第一个 Python 程序
- 使用文本编辑器
- Python代码运行助手
- 输入和输出
- 源码
- learning.py
- Python基础
- 数据类型和变量
- 字符串和编码
- 使用list和tuple
- 条件判断
- 循环
- 使用dict和set
- 函数
- 调用函数
- 定义函数
- 函数的参数
- 递归函数
- 高级特性
- 切片
- 迭代
- 列表生成式
- 生成器
- 迭代器
- 函数式编程
- 高阶函数
- map/reduce
- filter
- sorted
- 返回函数
- 匿名函数
- 装饰器
- 偏函数
- Python函数式编程——偏函数(来自博客)
- 模块
- 使用模块
- 安装第三方模块
- 面向对象编程
- 类和实例
- 访问限制
- 继承和多态
- 获取对象信息
- 实例属性和类属性
- 面向对象高级编程
- 使用__slots__
- 使用@property
- 多重继承
- 定制类
- 使用枚举类
- 使用元类
- 错误、调试和测试
- 错误处理
- 调试
- 单元测试
- 文档测试
- IO编程
- 文件读写
- StringIO和BytesIO
- 操作文件和目录
- 序列化
- 进程和线程
- 多进程
- 多线程
- ThreadLocal
- 进程 vs. 线程
- 分布式进程
- 正则表达式
- 常用内建模块
- datetime
- collections
- base64
- struct
- hashlib
- itertools
- contextlib
- XML
- HTMLParser
- urllib
- 常用第三方模块
- PIL
- virtualenv
- 图形界面
- 网络编程
- TCP/IP简介
- TCP编程
- UDP编程
- 电子邮件
- SMTP发送邮件
- POP3收取邮件
- 访问数据库
- 使用SQLite
- 使用MySQL
- 使用SQLAlchemy
- Web开发
- HTTP协议简介
- HTML简介
- WSGI接口
- 使用Web框架
- 使用模板
- 异步IO
- 协程
- asyncio
- async/await
- aiohttp
- 实战
- Day 1 - 搭建开发环境
- Day 2 - 编写Web App骨架
- Day 3 - 编写ORM
- Day 4 - 编写Model
- Day 5 - 编写Web框架
- Day 6 - 编写配置文件
- Day 7 - 编写MVC
- Day 8 - 构建前端
- Day 9 - 编写API
- Day 10 - 用户注册和登录
- Day 11 - 编写日志创建页
- Day 12 - 编写日志列表页
- Day 13 - 提升开发效率
- Day 14 - 完成Web App
- Day 15 - 部署Web App
- Day 16 - 编写移动App
- FAQ
- 期末总结