本文主要是介绍python泛映射类型,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
目录
- 泛映射类型定义
- 泛映射类型抽象基类
- 字典构建
- 字典推导式
- 处理找不到的键
- get()方法
- setdefault方法
- 特殊方法__missing__
- __missing__方法应用场景
- __missing__使用例子
- k in my_dict.keys()
- 字典变种
- collections.defaultdict
- collections.OrderedDict
- collections.ChainMap
- collections.Counter
- collections.UserDict
- 不可变映射类型
- 集合论
- dict和set的背后机制
- dict与散列表
- set/frozenset与散列表
泛映射类型定义
- 泛映射类型即键值对类型,最常见的当然就是字典,键值对中的键必须是可散列的,可散列对象要满足以下要求:
- 在此对象的生命周期中,散列值不变
- 需要实现特殊方法__hash__
- 要有__qe__方法
- 若两个可散列对象相等,则其散列值一定相等
- 常见可散列对象:
- 原子不可变数据类型,如str、bytes、数组类型
- frozenset
- 若元组中元素均可散列,则元组对象可散列
- 自定义对象可散列,散列值即id()返回值,即内存地址
泛映射类型抽象基类
字典构建
- 字典构建有很多种方式,可以用{}也可以用dict()。需要注意的是字典是无序的,即构建字典时键值对的顺序不重要。
字典推导式
- 字典推导式和列表推导式,生成器表达式格式类似,只不过for循环中的可迭代对象的元素是键值对,即两个元素的元组。
name_id = [('xiaoming', 1), ('xiaohong', 2), ('xiaogang', 3)]
d1 = {name: id for name, id in name_id}
d2 = {id: name for name, id in name_id}
处理找不到的键
get()方法
- 当我们用d[k]来获取字典d的键k对应的值的时候,会发生以下几个步骤:
- 首先查找字典d有没有键k
- 如果有,根据k的散列值找到对应的值
- 如果没有,抛出异常
- 当键k不存在时,异常处理比较麻烦,因此可以用d.get(k, default)来代替d[k],此时键k若不存在则不会抛出异常,而是返回default。
- 用get()方法存在一个问题,如果我们不是为了查找键k的值,而是为了给键k赋值,即d.get(k, default) = num,此时若键k不存在,则num会被赋值给default,这显然是不符合逻辑的。对于这种情况,要使用setdefault方法。
setdefault方法
- setdefault方法定义
d.setdefault(k, [default])
- 若字典中有键k,则将它对应的值置为default
- 若字典中无键k,则把default赋值给键k,即d[k] = default,然后返回default
- setdefault方法用于给字典某个键重新赋值或重新插入一个键值对
特殊方法__missing__
__missing__方法应用场景
- 映射类型通过__getitem__方法找不到键的时候,会自动调用__missing__方法。比如d[k]找不到键k。
- 通过get或__contains__方法(in操作符底层)找不到键时不会调用__missing__。
__missing__使用例子
- 下边例子中,__missing__方法里先判断键key是不是str,是为了防止无限递归。否则就会发生以下步骤:
- __getitem__找不到键,调用__missing__
- __mising__返回self[str(key)],由于key本身就是字符串,str(key) == key,因此又会调用__getitem__
- 然后__getitem__找不到,继续调用__missing__
- 该例子也说明了__missing__的用法,有时候键是字符串,而我们没有输入字符串,于是找不到(比如1和'1'),这个时候自动调用__missing__将键转成字符串重新查找。
class StrKeyDict0(dict): # StrKeyDict0继承自dict
def __missing__(self, key):
if isinstance(key, str):
raise KeyError(key) # __missing__是用来处理找不到的键的,如果找不到的键本身就是字符串,那么会触发KeyError异常
return self[str(key)] # 如果找不到的键本身不是字符串,那么就转成字符串再查找一次
def get(self, key, default=None):
try:
return self[key] # get方法这里实际上把查找工作委托给__getitem__了,因为这里用了【】索引
except KeyError: # 如果抛出了KeyError,那么说明__missing__也失败了,就直接返回default了
return default
def __contains__(self, key): # 先按照传入的键来查找,如果找不到就转换成字符串再找一次
return key in self.keys() or str(key) in self.keys()
k in my_dict.keys()
- 该操作查找键是否存在在python3中是很快的,即便映射类型对象很大也没关系,因为dict.keys()返回的是一个视图,在视图里查找元素速度很快。
字典变种
collections.defaultdict
collections.OrderedDict
- 普通字典是无序的,但是该有序字典不是说按照键的字典序排序,而是说保持插入顺序。
collections.ChainMap
collections.Counter
collections.UserDict
不可变映射类型
- 标准库中映射类型均可变,types模块中的MappingProxyType,其初始化参数接受一个映射对象,返回一个只读的映射视图,该视图虽不能更改,但是原始初始化参数的改变可以反应到该视图上。
集合论
set&frozenset
- python中集合类主要有set和frozenset两种。set对象是不可散列的,frozenset对象是可散列的。
- 集合的本质是许多唯一对象的集合,所谓唯一对象指的是集合中的元素都是不同的,因此集合可以用于去重。
- 集合的背后是散列表,因此查询速度极快。
- 集合中的元素必须可散列,因此如果要定义嵌套集合对象,则里层元素不能是set对象,可以是frozenset对象。
- 集合实现了交并差(& | -)等操作。
集合字面量
- set定义集合两种方式:
- 直接用{}定义,s = {1, 2, 3}
- 用set定义,s = set([1, 2, 3])
- 直接定义速度更快,因为用set()定义的时候,python会先去查询set的构造函数,然后新建一个列表,最后把列表传入到构造函数里,但是直接用 { }的话python会利用一个专门的叫做BUILD_SET的字节码来创建集合。
- 空集合定义只能用 s = set()而不能是s = {},因为后者定义的是字典。
- frozenset定义集合只能用构造函数,f = frozenset(range(10))。
集合推导式
- s = {i for i in range(10)}
dict和set的背后机制
dict与散列表
- 散列表是一个稀疏数组,其中单元叫表元,在dict中,每个键值对占用一个表元,即表元分为两部分,一个是对键的引用,一个是对值的引用。
- python保证散列表中三分之一表元是空的,快到这个阈值时散列表会被复制到一个更大的空间中。
- 散列表中每个表元大小相同,因此可根据键的散列表确定偏移量,通过偏移量定位到某个表元。因此dict中键必须可散列。
- python中计算某个元素散列值用hash()实现。
- 字典在内存上开销巨大,因为散列表是稀疏的,是典型的空间换时间,键查询很快,常数时间复杂度访问。
- 键的次序取决于添加顺序,虽然字典无序,但是键在字典中的顺序是不同的,如果两个键发生散列冲突,则后添加的键会被安排到另一个位置。
- 添加新键会改变已有键的顺序。因为无论合适添加,python都有可能为字典扩容,新建一个更大的散列表,过程中可能发生新的散列冲突,导致散列表中键的次序变化。因此,不要在循环字典的同时修改,因此可能因为顺序变化而导致跳过一些键。
set/frozenset与散列表
- set和frozenset背后也是散列表,表元中只存储键的引用,没有值的引用。
- 集合中元素必须可散列。
- 集合很占内存。
- 可以快速判断元素是否在某个集合。
- 元素次序取决于添加到集合的次序。
- 添加元素可能改变集合里已有元素的次序。
这篇关于python泛映射类型的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!