十二、容器——字典
1. 字典概述
- 定义
dictionary
(字典) 是 除列表以外 Python
之中 最灵活 的数据类型
和列表的区别
- 列表 是 有序 序列
- 字典是“键值对”的 无序可变序列(3.5之后是有序的)
- 字典定义格式
d = {key1: value1, key2:value2, ...}
d = {}
d = dict()
b = {'name':'gaoqi','age':18,'job':'programmer'}
b = dict(name='gaoqi',age=18,job='programmer')
b = dict([("name","gaoqi"),("age",18)])
通过 zip()创建字典对象
k = ['name','age','job']
v = ['gaoqi',18,'techer']
d = dict(zip(k,v))
d # {'name': 'gaoqi', 'age': 18, 'job':
- 特点
- 键
key
是索引 - 值
value
是数据 - 值 可以取任何数据类型,但 键 只能使用不可变类型,即 字符串、数字或 元组
- key一般情况下用字符串类型数据充当
- 即可hash 的对象都可以作为key
- 理论上所有不可变的类型都可以作为key
- 每一个键值对是一个元素
- 键必须是唯一的
xiaoming = {"name": "小明",
"age": 18,
"gender": True,
"height": 1.75}
用法总结:
-
键必须可散列 (hash)
- 数字、字符串、元组,都是可散列的。
- 自定义对象需要支持下面三点:
- 支持
hash()
函数 - 支持通过
__eq__()
方法检测相等性。 - 若
a == b
为真,则hash(a) == hash(b)
也为真。
- 支持
-
字典在内存中开销巨大,典型的空间换时间。
-
键查询速度很快
-
往字典里面添加新建可能导致扩容,导致散列表中键的次序变化。因此,不要在遍历字 典的同时进行字典的修改。
- 应用场景
- 尽管可以使用
for in
遍历 字典 - 但是在开发中,更多的应用场景是:
- 使用 多个键值对,存储 描述一个
物体
的相关信息 —— 描述更复杂的数据信息 - 将 多个字典 放在 一个列表 中,再进行遍历,在循环体内部针对每一个字典进行 相同的处理
- 使用 多个键值对,存储 描述一个
card_list = [{"name": "张三",
"qq": "12345",
"phone": "110"},
{"name": "李四",
"qq": "54321",
"phone": "10086"}
]
2. 元素访问
使用字典获取字典中的数据
格式:
字典变量名[key]
- 区别:访问时,如果用了不存在的key,会报错
字典变量名.get(key)
【推荐】- 区别:访问时,如果用了不存在的key,会返回None
注意
- 字典也是通过下标(key)方式来访问元素,但是字典没有索引(下标编号)
3. 遍历
- 键、键值显示方法
- items():列出所有的键值对
- keys():列出所有的键
- values():列出所有的值
- 四种遍历方式
方式1
for k in d:
print(k,':',d[k])
方式2
# keys()方法
for k in d.keys(): # 这里的`d.keys`是列表
print(k,':',d[k])
- 这里的
d.keys
是列表
方式3
# values()方法
for v in d.values(): # 这里的`d.values`是列表
print(v)
- 这里的
d.values
是列表 - 这里只能获取到值,无法获取key
方式4
# items()方法1
for item in d.items():# 这里的`d.items`是有元组元素的列表
print(item)
# items()方法2
for k,v in d.items(): # 这里是解包过程
print(k, ':', v)
4. 字典增删改查
增
- 利用
字典变量名[key]
来赋值,如果key在字典中不存在,就是向字典增加数据
d['a'] = 1
d['b'] = 2
update()
:新字典中所有键值对全部添加到旧字典对象上。- 如果 key 有重复,则直 接覆盖。
a = {'name':'gaoqi','age':18,'job':'programmer'}
b = {'name':'gaoxixi','money':1000,'sex':'男的'}
a.update(b)
a # {'name': 'gaoxixi', 'age': 18, 'job': 'programmer', 'money': 1000, 'sex': '男的'}
删
关键字 / 函数 / 方法 | 说明 |
---|---|
popitem() | 删除末位的一个键值对 |
pop(key) | 指定key来删除任意键值对 并返回对应的“值对象” |
clear() | 清空字典 |
del d[key] | 删除指定key的键值对【关键字】 |
del d | 删除字典【关键字】 |
del(d[key]) | 删除指定key的键值对【函数】 |
del(d) | 删除字典【函数】 |
改
如果赋值时的key在字典中存在,则通过key来修改该值
d[key] = 新的值
注意
- 字典中的key具有唯一性
- 如果key不存在,就是向字典中添加值
- key是不可以修改的
如果想要修改key,只能先删除key,在重新添加键值对
查
同字典的引用,访问
-
字典变量名[key]
- 区别:访问时,如果用了不存在的key,会报错
-
字典变量名.get(key)
- 区别:访问时,如果用了不存在的key,会返回空值
-
len()
键值对的个数 -
'key' in d
:检测一个“键”是否在字典中
*字典练习(名片管理)
函数一: 建立名片-> 建立好的名片
函数二: 显示名片-> 接收谁打印谁
# 定义字典函数
def creat_card():
# 使用字典来保存每张名片的数据
# name,age,address
card = {}
card['name'] = input('请输入姓名')
card['age'] = input('请输入年龄')
card['address'] = input('请输入地址')
return card
# 显示字典内容函数
def show_card(card):
for k, v in card.items():
print(f'{k},{v}')
card=creat_card()
show_card(card)
5. 无序字典和有序字典(了解)
- 在 python3.5之前, dict 类型是无序的 ,key无序
- 写入顺序和显示顺序不同
- 在 pyhton3.5之后,都是有序
- 写入顺序和显示顺序相同
6. 字典核心底层原理(了解)
- 核心底层原理
字典对象的核心是散列表。散列表是一个稀疏数组(总是有空白元素的数组),数组的 每个单元叫做 bucket。每个 bucket 有两部分:一个是键对象的引用,一个是值对象的引 用。
bucket
:桶,或者叫表元
由于,所有 bucket 结构和大小一致,我们可以通过偏移量来读取指定 bucket。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GiWbte4t-1610202477509)(Media/image-20201230202700268.png)]
- 将一个键值对放进字典的底层过程
a = {}
a["name"]="gaoqi"
假设字典 a 对象创建完后,数组长度为 8:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-548g5wam-1610202477511)(Media/image-20201230202821664.png)]
我们要把”name”=”gaoqi”这个键值对放到字典对象 a 中,首先第一步需要计算 键”name”的散列值。Python 中可以通过 hash()来计算。
bin(hash("name")) # '-0b1010111101001110110101100100101'
由于数组长度为 8,我们可以拿计算出的散列值的最右边 3 位数字作为偏移量,即 “101”,十进制是数字 5。我们查看偏移量 5,对应的 bucket 是否为空。如果为空,则 将键值对放进去。如果不为空,则依次取右边 3 位作为偏移量,即“100”,十进制是数字4。再查看偏移量为 4 的 bucket 是否为空。直到找到为空的 bucket 将键值对放进去。流 程图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tSCuWJZd-1610202477513)(Media/image-20201230203031821.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pqTOHpOx-1610202477516)(Media/image-20201230203040063.png)]
- 扩容
python 会根据散列表的拥挤程度扩容。“扩容”指的是:创造更大的数组,将原有内容 拷贝到新数组中。 接近 2/3 时,数组就会扩容。
- 根据键查找“键值对”的底层过程
我们明白了,一个键值对是如何存储到数组中的,根据键对象取到值对象,理解起来就 简单了。
a.get("name") # 'gaoqi'
当我们调用 a.get(“name”),就是根据键“name”查找到“键值对”,从而找到值 对象“gaoqi”。
第一步,我们仍然要计算“name”对象的散列值:
> bin(hash("name")) # '-0b1010111101001110110101100100101'
和存储的底层流程算法一致,也是依次取散列值的不同位置的数字。 假设数组长度为 8,我们可以拿计算出的散列值的最右边 3 位数字作为偏移量,即“101”,十进制是数字 5。我们查看偏移量 5,对应的 bucket 是否为空。如果为空,则返回 None。如果不为空, 则将这个 bucket 的键对象计算对应散列值,和我们的散列值进行比较,如果相等。则将对 应“值对象”返回。如果不相等,则再依次取其他几位数字,重新计算偏移量。依次取完后, 仍然没有找到。则返回 None。流程图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ADmLRLv5-1610202477518)(Media/image-20201230203301791.png)]
**打印表格练习
姓名 | 年龄 | 薪资 | 城市 |
---|---|---|---|
高小一 | 18 | 30000 | 北京 |
高小二 | 19 | 20000 | 上海 |
高小五 | 20 | 10000 | 深圳 |
r1 = {"name":"高小一","age":18,"salary":30000,"city":"北京"}
r2 = {"name":"高小二","age":19,"salary":20000,"city":"上海"}
r3 = {"name":"高小五","age":20,"salary":10000,"city":"深圳"}
tb = [r1,r2,r3]
#获得第二行的人的薪资
print(tb[1].get("salary"))
#打印表中所有的的薪资
for i in range(len(tb)): # i -->0,1,2
print(tb[i].get("salary"))
#打印表的所有数据
for i in range(len(tb)):
print(tb[i].get("name"),tb[i].get("age"),tb[i].get("salary"),tb[i].get("city"))