模块
collections.abc
中的两个抽象基类
Mapping
与
MutableMapping
为
dict
和其他类似类型定义了形式接口,然而非抽象映射类型一般不继承自这两个抽象基类,而是拓展
dict
或
collections.UserDict
。这些抽象基类的主要作用是作为形式化文档,定义构建一个映射类型所需要的最基本的接口,还可以搭配
isinstance
运算符来判定某个数据是不是广义上的映射类型。
若要自定义一个映射类型,更合适的策略是继承自
collections.UserDict
类
标准库里的所有映射类型都是通过
dict
来实现的,故只有可散列的属性类型才能用作这些映射的键。
一、可散列对象
实现了
__hash__()
方法的对象,其散列值在其生命周期中是不变的。同时还要实现
__eq__()
,这样才能与其他键作比较,
如果两个可散列对象是相等的,那么它们的散列值一定相等
。一般来讲用户自定义的类型的对象都是可散列的,且使用它们的
id()
值作为散列值,在比较的时候这些对象都不相等。若一个对象实现了
__eq__
方法,且在方法实现中用到了对象的内部状态,则只有所有内部的状态是不可变的情况下,该对象才是可散列的。
在内置类型中:原子不可变数据类型都是可散列的,而对于原子可变的不可变序列,若其元素均为可散列的,则其可散列。而所有的可变序列,都是不可散列的。
可散列:实现
__hash__()
方法,且该方法返回一个固定不变的、可比较的值
二、字典推导
字典推导从
以键值对作为元素
的可迭代对象中构建出字典。
DIAL_CODES =[
(86,'China'),
(91,'India'),
(1,'United States'),
(55,'Brazil'),
]
{country:code for code,coutry in DIAL_CODES}
>> {
'China':86,
'India':91,
'United States':1,
'Brazil':55,
}
即该可迭代对象中的元素是以对的形式存在的。而对于使用两个
for
子句的推导,不能得到正确形式的字典:
{ x:y for x in range(4) for y in "abcd"}
>> {
0:'d',
1:'d',
2:'d',
3:'d',
}
原因是这种形式的推导实际上是产生:
{
0:'a', 1:'a', 2:'a', 3:'a',
0:'b', 1:'b', 2:'b', 3:'b',
0:'c', 1:'c', 2:'c', 3:'c',
0:'d', 1:'d', 2:'d', 3:'d',
}
而先生成的键值对被后生成的键值对所覆盖。
推而广之,在 Python 中可以用一个映射对象来新建一个映射对象,也可以用包含
(key,value)
元素的可迭代对象初始化映射对象。
三、映射方法
方法
d.clear()
移除字典中所有元素
d.pop(key,[default])
返回键
key
对应的值,并从字典中移除该键值对;若不存在
key
,则返回
default
(默认为None)
d.popitem()
随机返回一个键值对并从字典里移除它
d.fromkeyset(iter,[initial])
将可迭代对象的元素设置为映射的键,使用
initial
参数作为这些键的值(默认为None)
d.get(key,[default])
返回键
key
对应的值,若键不存在,返回
default
(默认为None)
d.setdefault(key,[default])
若存在键
key
,则返回对应值;否则,令
d[key]=default
,然后返回
default
(默认为None)
d.update(m,[**kargs])
m
为映射或键值对迭代器,用以更新字典中对应条目*(批量更新)*
四、映射的弹性查询
进行映射查询时,我们希望能够处理找不到的键,得到一个默认值。一般有两种方式能够实现:
1.defaultdict
在实例化
collections.defaultdict
的时候,给构造方法提供一个可调用对象,当
__getitem__
遇到找不到的键的时候调用,作为目标键的值添加键值对。
如:新建字典
dd = collections.defaultdict(list)
,当键
new-key
在
dd
不存在的话,
dd['new-key']
会执行以下步骤:
调用
list()
创建空列表;
将新列表作为值,
new-key
作为键,添加到
dd
中;
返回新列表的引用
若在创 defaultdict 的时候没有指定可调用对象,则查询不存在的键会触发
KeyError
。
defaultdict 的可调用对象仅在
__getitem__
里被调用,在其他方法里完全不会起作用(如get)。
2.实现特殊方法__missing__
所有映射类型遇到找不到的键的时候,都会调用
__missing__
(
defaultdict
也是通过该方法实现)。可以通过这个方法自定义对象找不到某个键时会发生什么。
该方法只会被
__getitem__
调用,对
get
或
__contains__
方法的使用没有任何影响。
五、子类化UserDict
对于自定义映射类型来说,使用
UserDict
作为基类比
dict
要更加方便:内置类型的方法中会忽略子类覆盖的方法,再次调用原有的方法。
而 UserDict 并非 dict 的子类,但其
data
属性是一个 dict 实例,实际存储了 UserDict 中的数据。在实现 UserDict 方法时候,可以将具体的字典操作代理给
data
属性。
六、集合
Python中的集合有:
set
;
不可变类型
frozenset
一、散列型:由于集合底层的实现,
集合中的元素必须是可散列的
。而 set 本身是不可散列的,但 frozenset 是可散列的。
二、构造:
对于 set 有两种构造方法:字面量法
{...}
与构造函数法
set(iter)
。一般推荐使用字面量法,因其速度更快。
对于 frozenset 只能通过构造函数来创建。
三、
集合推导
:集合推导类似于列表推导,只不过将
[]
换为
{}
:
{i for i in range(32,256)}
集合操作
对于集合操作,若为中缀运算符,则要求两侧的被操作对象均为集合;但若为其他操作,则只要求传入的参数是可迭代对象
。
运算
运算符
|
求并集
&
求交集
-
求差集
e in s
判断元素
e
是否属于集合
s
<
/
<=
(
>
/
>=
)
判断集合是否包含/真包含
集合方法
方法
s.add(e)
将元素
e
添加到
s
s.clear()
删除
s
中所有元素
s.cop()
浅复制
s
s.discard(e)
若
s
中有
e
,则移除这个元素
s.pop()
从
s
中移除一个元素并返回
对于以上方法,若方法会原地修改集合,则不能用于 frozenset。
七、散列
散列表为字典与集合实现的底层机制,而基于这个机制
字典和集合的运算速度都很快
。
1. 散列表
散列表为一个稀疏数组,表中的单元称表元。在字典的散列表中,每个键值对都占用一个表元,每个表元都有两个部分:对键的引用、对值的引用。而在集合的散列表中,存放的只有元素的引用(相当于只有键的字典)。Python 会设法保证至少 1/3 是空的,当达到这个阈值时,原有散列表会被复制到一个更大的空间中。
散列与相等:
散列表使用散列值来索引
。若两个对象比较时是相等的,则它们的散列值应该相等,否则散列表不能正常运行。
Python的散列查找算法:
而对于添加新元素与更新键值对,过程几乎与上面一样,只不过对于添加,在发现空表元的时候会放入新元素;对于更新,在找到相应表元后会替换新值。
散列表的使用导致了字典空间效率的低下:①散列表的稀疏性 ②表元中同时存储了键与值。
即,字典是一种用时间换取空间的数据结构.
键的次序取决于添加的顺序:往
dict
添加新键发生冲突时,会进行再散列,将新建存放到另一个位置,故键值对的次序取决于其添加进字典的顺序。
这就导致了相等的字典键值对顺序可能不一致
(添加键值对的过程中发生了冲突)。
往字典里添加新建可能改变已有键的顺序
,当添加一个键导致扩容时,新的散列表的顺序很有可能被打乱。如果在迭代字典的过程中,对字典进行修改,那么该循环很有可能通过一些键或重复迭代某些键。
利用反汇编函数
dis.dis
可以查看代码运行翻译成的字节码。
*其他字典:
collections.OrderedDict:有序字典,保持键添加的顺序不变;
collections.Counter:给键添加一个整数计数器,每次更新一个键的时候增加这个计数器