"""
dict广泛应用于模块的命名空间实例的属性函数的关键字参数
它的内置函数都放在__builtins__.__dict__模块中
散列表是字典背后的数据结构
集合的实现也依赖散列表
"""
"""本章内容大纲
1.常见的字典方法
2.如何处理查找不到的键
3.标准库中dict的变种
4.set和frozenset类型
5.散列表的工作原理
6.散列表的潜在影响
什么样的数据类型可作为键
不可预知的顺序"""
# 3.1 泛映射类型
import collections.abc
"""Mapping
MutableMapping
这两个抽象基类的作用:
为dict和其他类似的类型定义形式接口
作为形式化的文档,他们定义了构建一个映射类型所需要的最基本的接口
可以跟isinstance判断某个数据是不是广义的映射类型"""
# 例:
# my_dict = dict()
# print(isinstance(my_dict, collections.abc.Mapping))
# >>>True
# tips:可散列的数据类型
"""如果一个对象时可散列的,那么在这个对象的生命周期中,它的散列值是不变的
需要实现__hash__方法
需要实现__eq__方法,和其他键作比较,
如果两个可散列对象相等,他们的散列值是一样的
可散列对象:
原子不可变数据类型都是可散列的:
str/bytes/数值类型
frozenset
元组
所有元素都为可散列的,元组才是可散列的
"""
# 例
# tt = (1,2,(30,40))
# print(hash(tt)) # >>>-3907003130834322577
# tl = (1,2,[30,40])
# hash(tl) # 报错 列表不可散列
# tf = (1,2,frozenset([30,40]))
# print(hash(tf)) # 5149391500123939311
# 一般来讲,所有用户创建的对象都是可散列的,散列值就是id()的返回值
# x = (100,)
# print(hash(x))
# print(id(x))
# >>>2279747396317938166
# >>>2411106468240
# 经过验证不同
# 创建字典的不同方式
# a = dict(one = 1, two = 2,three = 3)
# b = {'one' : 1,'two' : 2,'three':3}
# c = dict(zip(['one','two','three'],[1,2,3]))
# d = dict([('two',2),('one',1),('three',3)])
# e = dict({'three':3,'two':2,'one':1})
# print(a == b == c == d == e) # >>>True
# 3.2字典推导式(dict comprehensive)
# 将一个装元组的列表变成两个不同的字典
# DIAL_CODES = [
# (86,'China'),
# (91,'India'),
# (1,'USA'),
# (62,'Indonesia'),
# (55,'Brazil'),
# (92,'Pakistan'),
# (880,'Bangladesh'),
# (234,'Nigeria'),
# (7,'Russian'),
# (81,'Japan')
# ]
# country_code = {country:code for code,country in DIAL_CODES}
# print(country_code)
#
# country_code_upper = {code:country.upper() for country,code in country_code.items()
# if code < 66}
# print(country_code_upper)
# 3.3 常见的映射方法
# 用setdefault处理找不到的键
# index0.py
# index.py
# 3.4映射的弹性键查询
'''某个键在映射里不存在
但希望通过这个键读取值的时候,能得到一个默认值
2种方法:
1.使用defaultdict
2.定义dict的子类,实现__missing__方法'''
# 3.4.1 defaultldict 处理找不到的键的一个选择
'''在用户创建defaultdict对象的时候,需要给构造方法提供一个可调用对象
这个可调用对象会在__getitem__碰到找不到的键的时候被调用
让__getitem__返回某种默认值
而这个用来生成默认值的可调用对象存放在default_factory的实例属性中'''
# 例子
# from collections import defaultdict
# dd = defaultdict(list)
# print(dd.default_factory) # 查看实例属性
# print(dd['one'])
'''
dd['one']过程分析:
1.调用list()来建立一个新列表
2.把这个新列表作为值,'one'作为它的键,放到dd中
3.返回这个列表的引用'''
# index_default.py
# 3.4.2 特殊方法__missing__
# 这个方法会在映射类型处理找不到的键的时候触发
# 示例 StrKeyDict0.py
# 3.5 字典的变种
import collections
# collections.OrderedDict
"""
这个类型在添加键的时候会保持顺序,因此键的迭代次序总是一致的
popitem方法默认删除并返回字典最后一个元素
my_odict.popitem(last = False)删除并返回第一个被添加的元素"""
# my_odict = collections.OrderedDict([(1,'one')])
# my_odict.update([(2,'two'),(3,'three')])
# my_odict.setdefault(4,'four')
# print(my_odict.get(4,'键不存在'))
# print(my_odict)
# print(my_odict.popitem())
# print(my_odict.popitem(last=False))
# collections.ChainMap
"""该类型可以容纳几个不同的映射对象
在进行键查找操作时,这些对象会被当做一个整体被逐个查找,直到键被找到为止
这个功能在给有嵌套作用域的语言做解释器的时候很有用"""
# import builtins
# pylookup = collections.ChainMap(locals(),globals(),vars(builtins))
# print(pylookup)
# collections.Counter
"""这个类型会给键准备一个整数计速器,每次更新一个键的时候都会增加这个计数器
作用:给可散列对象计数
或者当成多重集合来使用,集合的元素可以出现不止一次
实现了+,-运算符来合并记录
most_common([n])会按次序返回最常见的n个键和他们的计数"""
# ct = collections.Counter('ababacedbderasdfssd')
# print(ct)
# ct.update('asdjkhfhjkasdgfasnbdjkasdnblsdgfbajsdfsagudjkashnfjksdhsdk')
# print(ct)
# ct1 = collections.Counter('sadjkasklddfh')
# print(ct-ct1)
# print(ct.most_common(3))
# collections.UserDict
"""这个类型其实是把标准的dict用纯python又实现了一遍
它是让用户继承写子类的"""
# 3.6子类化UserDict
# UserDict不是dict的子类,但是有一个data属性,是dict的实例,这个属性实际上是最终存储数据的地方
# 示例:StrKeyDict.py
# 3.7不可变映射类型
# 标准库中没有,types模块中有一个MappingProxyType类
# 会返回一个只读的映射视图,无法通过视图对原映射做修改
# from types import MappingProxyType
# d = {1:'A'}
# d_proxy = MappingProxyType(d)
# print(d_proxy)
# print(d_proxy[1]) # d中的内容可以通过d_proxy看到
# # d_proxy[2] = 'x' # 报错,不能修改
# d[2] = 'B' # 对d 的修改会反馈到d_proxy上
# print(d_proxy)
# print(d_proxy[2])
# print(d)
# 3.8集合论
"""集合的本质是许多唯一对象的聚集,因此可以用于去重
集合的元素必须是可散列的
中缀运算符: 两个集合a,b
a | b 合集
a - b 差集
a & b 交集"""
# 3.8.1集合字面量
"""
空集:set()
"""
# from dis import dis # 反汇编函数
# dis('{1}')
# dis('set([1])')
"""
1 0 LOAD_ConST 0 (1) # 检查{1}字面量背后的字节码
2 BUILD_SET 1 # 特殊的BUILD_SET几乎完成了所有工作
4 RETURN_VALUE
1 0 LOAD_NAME 0 (set) # set([1])的字节码
2 LOAD_ConST 0 (1)
4 BUILD_LIST 1
6 CALL_FUNCTION 1
8 RETURN_VALUE"""
# python没有frozenset的字面量表示法,只能采用构造方法
# print(frozenset(range(1,10)))
# 3.8.2集合推导
"""from unicodedata import name
set1 = {chr(i) for i in range(32,256) if 'SIGN' in name(chr(i),'')}
print(set1)
print([name(chr(i),'') for i in range(32,256)])"""
# 3.8.3集合的操作
"""集合运算:
两个集合a,b
a | b 合集
a - b 差集
a & b 交集
比较运算符:
e in a
a > b />=
a < b /<=
其他方法:
a.add(e) :把3添加到集合a中
a.clear() : 清空集合
a.copy() : 返回a的浅复制
a.discard(e) : 如果e存在,移除它
a.pop()
a.remove()"""
# a = {1,2,3,4}
# a.discard(5) # 不会报错
# a.remove(5) # KeyError
# 3.9 dict和set的背后
"""
dict 和 set 的效率有多高?
为什么他们是无序的?
为什么并不是所有的python对象都可以作为dict的键或者set的元素?
为什么dict的键和set的元素的顺序是根据他们被添加的次序决定的,在映射对象的生命周期中,这个顺序并不是一成不变的
为什么不应在迭代循环dict或者set的时候往里面添加元素?"""
# 3.9.1 效率
# 3.9.2 字典中的散列表
# 内置的hash()方法可以用于所有的内置类型对象
# 3.9.3 dict的实现及其导致的结果
"""键必须是可散列的
一个可散列对象必须满足以下要求:
1.支持hash()函数,并且通过__hash__得到的散列值是不可变的
2.支持通过__eq__来检测相等性
3.若 a==b,则hash(a) == hash(b)
4.字典在内存上开销巨大
5.如果需要存放数量巨大的记录,那么放在元组或者具名元组构成的列表中会是比较好的选择
6.键查询很快
7.键的次序取决于添加顺序
由dict([(key1,value1),(key2,value2)])
和dict([(key2,value2),(key1,value1)])得到的两个字典
比较的时候是相等的,但如果在key1和key2被添加的字典的时候有冲突发生
这两个键出现在字典里的顺序是不一样的"""
# 例
# DIAL_CODES = [
# (86,'China'),
# (91,'India'),
# (1,'USA'),
# (62,'Indonesia'),
# (55,'Brazil'),
# (92,'Pakistan'),
# (880,'Bangladesh'),
# (234,'Nigeria'),
# (7,'Russian'),
# (81,'Japan')
# ]
# d1 = dict(DIAL_CODES)
# print('d1:', d1.keys())
# d2 = dict(sorted(DIAL_CODES))
# print('d2:', d2.keys())
# d3 = dict(sorted(DIAL_CODES,key=lambda x:x[1]))
# print('d3:', d3.keys())
# assert d1 == d2 and d2 == d3 # 这些字典是相等的,因为包含的数据是一样的
"""结果:
d1: dict_keys([86, 91, 1, 62, 55, 92, 880, 234, 7, 81])
d2: dict_keys([1, 7, 55, 62, 81, 86, 91, 92, 234, 880])
d3: dict_keys([880, 55, 86, 91, 62, 81, 234, 92, 7, 1])"""
# 往字典里添加新键可能会改变已有键的顺序
# 因为添加新键可能涉及字典扩容
# 因此不要在迭代循环一个字典的时候修改字典
# 3.9.4 set的实现以及导致的结果
"""
1.集合中的元素必须是可散列的
2.集合痕消耗内存
3.可以痕高效地判断元素是否存在于集合中
4.元素的次序取决于添加到集合的次序
5.往集合里添加元素,可能会改变集合里已有元素的次序"""
# 3.10 总结
"""
1.字典是python的基石
2.除了dict外,标准库还提供好用的映射类型 defaultDict OrderedDict Counter ChainMap
3.以及便于用户扩展的UserDict
4.大多数映射类型都提供两个痕强大的方法 update 和 setdefault
5.映射类型的API中有个很好用的__missing__方法,用于对象找不到某个键的时候,通过这个
方法自定义会发生什么
6.collections.abc提供两个抽象基类 Mapping MutableMapping
利用他们可以进行类型查询或者引用
7.types模块中的MappingProxyType可以用来创建不可变映射类型
8.dict和set高效率的背后是因为使用了散列表作为数据结构
同时提高效率的代价是以牺牲空间换来的"""
index0.py
'''创建一个从单词到其出现情况的映射'''
import sys
import re
WORD_RE = re.compile(r'w+')
index = {}
with open(sys.argv[1],encoding='utf-8') as fp:
for line_no,line in enumerate(fp,1):
for match in WORD_RE.finditer(line):
word = match.group()
column_no = match.start() + 1
location = (line_no,column_no)
occurrences = index.get(word,[])
occurrences.append(location)
index[word] = occurrences
# 以字母顺序打印结果
for word in sorted(index,key = str.upper):
print(word,index[word])
'''控制台执行的部分结果
six [(7, 1), (7, 5), (7, 9), (7, 13), (7, 17), (7, 21), (7, 26)]
ten [(12, 1), (15, 1), (18, 1)]
three [(3, 1), (3, 7), (3, 13)]
two [(2, 1), (2, 5), (2, 9)]'''
index.py
'''创建一个从单词到其出现情况的映射'''
import sys
import re
WORD_RE = re.compile(r'w+')
index = {}
with open(sys.argv[1],encoding='utf-8') as fp:
for line_no,line in enumerate(fp,1):
for match in WORD_RE.finditer(line):
word = match.group()
column_no = match.start() + 1
location = (line_no,column_no)
# 如果单词不存在,把单词和一个空列表放进映射,然后返回这个空列表
index.setdefault(word,[]).append(location)
# 以字母顺序打印结果
for word in sorted(index,key = str.upper):
print(word,index[word])
index_default.py
'''创建一个从单词到其出现情况的映射'''
import sys
import re
from collections import defaultdict
WORD_RE = re.compile(r'w+')
index = defaultdict(list) # 使用list构造方法作为default_factory来创建defaultdict
with open(sys.argv[1],encoding='utf-8') as fp:
for line_no,line in enumerate(fp,1):
for match in WORD_RE.finditer(line):
word = match.group()
column_no = match.start() + 1
location = (line_no,column_no)
# 如果在创建defaultdict时没有指定default_factory,查询不存在的键会触发KeyError
index[word].append(location)
# 以字母顺序打印结果
for word in sorted(index,key = str.upper):
print(word,index[word])
mappingproxytype.py
from types import MappingProxyType
d = {1:'A'}
d_proxy = MappingProxyType(d)
print(d_proxy)
print(d_proxy[1])
# print(d_proxy[2])
d[2] = 'B'
print(d_proxy)
print(d_proxy[2])
StrKeyDict0.py
# 实现一个dict的子类,在查询的时候把非字符串的键转换成字符串
class StrKeyDict(dict): # 继承了dict
# 实现__missing__方法为找不到的键提供默认值
def __missing__(self,key):
if isinstance(key,str): # 如果找不到的键本身就是字符串,抛出异常
raise KeyError(key)
return self[str(key)] # 如果找不到的键不是字符串,那么转化成字符串再查找
def get(self,key,default = None):
try:
return self[key]
except KeyError:
return default
def __contains__(self, key):
return key in self.keys() or str(key) in self.keys()
if __name__ == '__main__':
sd = StrKeyDict([('2','two'),('4','four')])
print(sd['2'])
print(sd[4])
# print(sd[1])
print(sd.get('2'))
print(sd.get(4))
print(sd.get(1, 'N/A'))
print(2 in sd)
print(1 in sd)
StrKeyDict.py
# 实现一个collections.UserDict的子类,无论是添加更新还是查询操作
# 都会把非字符串的键转化为字符串
import collections
class StrKeyDict(collections.UserDict): #
# 实现__missing__方法为找不到的键提供默认值
def __missing__(self,key):
if isinstance(key,str):
raise KeyError(key)
return self[str(key)]
def __contains__(self, key):
return str(key) in self.data
def __setitem__(self, key, value):
self.data[str(key)] = value
if __name__ == '__main__':
sd = StrKeyDict([('2','two'),('4','four')])
print(sd['2'])
print(sd[4])
# print(sd[1])
print(sd.get('2'))
print(sd.get(4))
print(sd.get(1, 'N/A'))
print(2 in sd)
print(1 in sd)
35岁学Python,也不知道为了啥!



