字典和集合

Posted welan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了字典和集合相关的知识,希望对你有一定的参考价值。

一、可散列的数据类型

字典至关重要,Python 对它的实现做了高度优化,而散列表则是字典类型性能出众的根本原因。集合(set)的实现其实也依赖于散列表,那么什么是可散列的呢?
如果一个对象是可散列的,那么在这个对象的生命周期中,它的散列值是不变的,而且这个对象需要实现 hash() 方法。另外可散列对象还要有 qe() 方法,这样才能跟其他键做比较。如果两个可散列象是相等的,那么它们的散列值一定是一样的。
原子不可变数据类型(str、bytes 和数值类型)都是可散列类型,frozenset 也是可散列的,因为根据其定义,frozenset 里只能容纳可散列类型。元组的话,只有当一个元组包含的所有元素都是可散列类型的情况下,它才是可散列的。来看下面的元组tt、tl 和 tf:

>>> tt = (1, 2, (30, 40))
>>> hash(tt)
8027212646858338501
>>> tl = (1, 2, [30, 40]) # 在这里元组tl中包含其他的不可散列的数据结构list
>>> hash(tl)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> tf = (1, 2, frozenset([30, 40]))
>>> hash(tf)
-4118419923444501110

上面例子得知,“python中所有的不可变数据类型都是可散列的”这种说法是错误的,因为像元组中可以包含对其他可变数据类型的引用。
一般来讲用户自定义的类型的对象都是可散列的,散列值就是它们的 id() 函数的返回值,所以所有这些对象在比较的时候都是不相等的。如果一个对象实现了 eq 方法,并且在方法中用到了这个对象的内部转状态的话,那么只有当所有这些内部状态都是不可变的情况下,这个对象才是可散列的。

二、字典的构建方式

1、 常用构建方法

>>> 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, 'one': 1, 'two': 2})
>>> a == b == c == d == e
True

2、 字典推导式

我们知道列表是可以通过推导式生成的

>>>ls = [i for i in range(3)]
>>>ls
[1, 2, 3]

在python中像字典和集合也是可以通过推导生成的

dict_list = [
    (1, 'a'),
    (2, 'b'),
    (3, 'c')
]
my_dict = {num: mystr for num, mystr in dict_list}
print(my_dict)
{1: 'a', 2: 'b', 3: 'c'}

三、dict查询

1、 dict内部方法

我们可以通过python的自省机制dir(dict)来查询到dict的内部方法
也可dict.__mro__查询到dict到底继承于谁
来看一下常见的内部方法的用途

常用方法 用途
d.clear() 移除所有元素
d.__contains__(k) 查询是否包含k元素
d.__delitem__(k) 删除键k,并删除相应的映射
d.get(k, None) 查找键k并返回相应的映射,如果没有返回None
d.__getitem__(k) 让字典 d 能用 d[k] 的形式返回键k 对应的值
d.__missing__(k) getitem 找不到对应键的时候,这个方法会被调用
d.items() 返回 d 里所有的键值对
d.keys() 获取所有的键
d.values() 返回字典里的所有值
d.__setitem__(k, v) 实现 d[k] = v 操作,把 k 对应的值设为v
d.setdefault(k,[default] 若字典里有键k,则把它对应的值设置为 default ,然后返回这个值;若无,则让 d[k] =default ,然后返回 default
d.popitem() 随机返回一个键值对并从字典里移除它
d.move_to_end(k,[last]) 把键为 k 的元素移动到最靠前或者最靠后的位置( last 的默认值是 True
d.update(m,[**kargs]) m 可以是映射或者键值对迭代器,用来更新 d 里对应的条目

注:

  • dict的亲戚有序字典OrderDict,OrderedDict.popitem() 会移除字典里最先插入的元素(先进先出);同时这个方法还有一个可选的 last 参数,若为真,则会移除最后插入的元素(后进先出)
  • update 方法处理参数 m 的方式,是典型的“鸭子类型”。函数首先检查 m 是否有 keys 方法,如果有,那么 update 函数就把它当作映射对象来处理。否则,函数会退一步,转而把 m 当作包含了键值对 (key, value) 元素的迭代器

当字典 d[k] 不能找到正确的键的时候,Python 会抛出异常,这个行为符合 Python 所信奉的“快速失败”哲学。也许每个 Python 程序员都知道可以用 d.get(k, default) 来代替 d[k],给找不到的键一个默认的返回值(这比处理 KeyError 要方便不少)。但是要更新某个键对应的值的时候,不管使用 getitem 还是 get 都会不自然,而且效率低,dict.get 并不是处理找不到的键的最好方法

2、setdefault处理查不到的键

示例

dict_list = [
    (1, 'a'),
    (2, 'b'),
    (3, 'c')
]
my_dict = {num: mystr for num, mystr in dict_list}
my_dict.get(4, 'd') # 当查询不到4这个键的时候,会返回一个‘d’
my_dict.get(4, 'd') 
my_dict[4] = 'd'
print(my_dict) # output{1: 'a', 2: 'b', 3: 'c', 4: 'd'}

上述例子不会抛出异常,但能做的仅此而已先查询,在追加而已,下面用setdefault

my_dict.setdefault(4,'d')

效果是一样的,但是对于后者来说,可以减少字典的查询次数

3、defaultdict处理查询不到的键

某个键在映射里不存在,我们也希望在通过这个键读取值的时候能得到一个默认值。有两个途径能帮我们达到这个目的,一个是通过 defaultdict 这个类型而不是普通的 dict,另一个是给自己定义一个 dict 的子类,然后在子类中实现 missing 方法。
在实例化一个 defaultdict 的时候,需要给构造方法提供一个可调用对象,这个可调用对象会在 getitem 碰到找不到的键的时候被调用,让 getitem 返回某种默认值。
我们新建了这样一个字典:dd = defaultdict(list),如果键 ‘new-key‘ 在 dd 中还不存在的话,表达式 dd[‘new-key‘] 会按照以 下的步骤来行事。
1 调用 list() 来建立一个新列表。
2 把这个新列表作为值,‘new-key‘ 作为它的键,放到 dd 中。
3 返回这个列表的引用。

import collections
dc = collections.defaultdict(list)
dc['c'].append('C')
dc # defaultdict(list, {'c': ['C']})

4、__missing__方法

所有的映射类型在处理找不到的键的时候,都会牵扯到 __missing__方法。这也是这个方法称作“missing”的原因。虽然基类 dict 并没有定义这个方法,但是 dict 是知道有这么个东西存在的。也就是说,如果有一个类继承了 dict,然后这个继承类提供了 missing 方法,那么在 getitem 碰到找不到的键的时候,Python 就会自动调用它,而不是抛出一个 KeyError 异常。
missing 方法只会被 getitem 调用(比如在表达式 d[k] 中)。提供 missing 方法对 get 或者contains(in 运算符会用到这个方法)这些方法的使用没有影响。

class StrKeyDict0(dict): 
    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() 
    

上面的类继承了Dict,但是有增加__missing__魔法函数
1 StrKeyDict0 继承了 dict。
2 如果找不到的键本身就是字符串,那就抛出 KeyError 异常。
3 如果找不到的键不是字符串,那么把它转换成字符串再进行查找。
4 get 方法把查找工作用 self[key] 的形式委托给 getitem,这样在宣布查找失败之前,还能通过 missing 再给某个键一个机会。
5 如果抛出 KeyError,那么说明 missing 也失败了,于是返回default。
6 先按照传入键的原本的值来查找(我们的映射类型中可能含有非字符串的键),如果没找到,再用 str() 方法把键转换成字符串再查找一次。
在上面的例子中,如果在__missing__没有instance判断key的类型,当key不是一个str(key)的键,代码就会无限递归。在进入get函数,return self[str(key)],进入getitem,查询失败,会进入__missing__函数,如果没有instance判断,则直接return self[str(key)],会接着查询调用getitem函数,这样持续查询,不会raise KeyError。所以instance的判断是必须的。
contains 方法在这里也是必需的。这是因为 kin d 这个操作会调用它,但是我们从 dict 继承到的 __contains__方法不会在找不到键的时候调用 missing 方法。__contains__里还有个细节,就是我们这里没有用更具 Python 风格的方式——k inmy_dict——来检查键是否存在,因为那也会导致 contains 被递归调用。为了避免这一情况,这里采取了更显式的方法,直接在这个self.keys() 里查询。
在python2中dict.keys()返回是一个列表
python3中的dict.keys()返回的像是一个视图,更像是一个集合,而像集合列表背后是散列表,典型的以空间换时间,查询会很快。

5、字典的亲戚

这些映射类型像defaultdict一样存在collections模块中

(1)、 collections.OrderedDict

这个类型在添加键的时候会保持顺序,因此键的迭代次序总是一致的。OrderedDict 的 popitem 方法默认删除并返回的是字典里的最后一个元素,但是如果像 my_odict.popitem(last=False) 这样调用它,那么它删除并返回第一个被添加进去的元素。

(2)、collections.ChainMap

该类型可以容纳数个不同的映射对象,然后在进行键查找操作的时候,这些对象会被当作一个整体被逐个查找,直到键被找到为止

(3)、collections.Counter

这个映射类型会给键准备一个整数计数器。每次更新一个键的时候都会增加这个计数器。
示例

>>> ct = collections.Counter('abracadabra')
>>> ct
Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
>>> ct.update('aaaaazzz')
>>> ct
Counter({'a': 10, 'z': 3, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
>>> ct.most_common(2)
[('a', 10), ('z', 3)]

集合

待完成

以上是关于字典和集合的主要内容,如果未能解决你的问题,请参考以下文章

13 个非常有用的 Python 代码片段

代码片段 - Golang 实现集合操作

laravel特殊功能代码片段集合

Python字典集合结构详解

Python代码阅读(第19篇):合并多个字典

带有红宝石集合/可枚举的酷技巧和富有表现力的片段[关闭]