《Fluent Python》读书笔记-Dictionaries and Sets

概览

    在collections.abc中定义了MappingMutableMapping去定义dict和类似类型的接口。一般的特殊类型的映射(mappings)都是继承自dict或者collections.UserDict

Hashable

    所有在标准库里的映射类型都是继承自dict,所以他们都有一个限制,key必须是hashable的。
    一个对象是hashable的有三点:
1.它有一个hash值在整个生命周期内都是不变的(需要一个__hash__()方法)。
2.能够和其他对象比较(需要一个__eq__()方法)
3.hashable的对象如果比较是相等的必须有相同的hash值。
    常见的hashable的类型:
1.原子的不可变类型(immutable)都是hashable的,比如str, bytes, numeric类型。
2.frozenset总是hashable的,因为它的元素要求必须都是hashable的。
3.tuple如果所有的元素都是hashable的就是hashable的。
4.用户自定义类型缺省是hashable的,因为他们的hash值就是他们的id()并且是不相等的。如果一个对象实现了自定义__eq__方法,就需要考虑到这个对象的内部状态,只有当所有的属性是immutable的这个对象才是hashable的。

>>> tt = (1, 2, (30, 40))
>>> hash(tt)
8027212646858338501
>>> tl = (1, 2, [30, 40])
>>> hash(tl)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> class A:
...     def __init__(self):
...         pass
... 
>>> a = A()
>>> b = A()
>>> hash(a)
-9223372036571095159
>>> hash(b)
283680646
>>> a == b
False

    关于__hash__()__eq__的自定义这里还有些特殊的要求,详细可以看python的手册中相关的内容
    这里还引申出另一个问题就是is==的区别。is比较的是对象的id是否相同,意义在于是否是同一个实例对象,是否是同一个内存地址。而==比较的是两个对象的内容是否相同,默认会调用对象的__eq__方法。继承自 object 的__eq__方法比较两个对象的id,结果与 is 一样。但是多数Python的对象会覆盖object的__eq__方法,而定义内容的相关比较,所以比较的是对象属性的值。

>>> a = [1, 2, 3]
>>> b = a
>>> b is a
True
>>> b == a
True
>>> b = a[:]
>>> b is a
False
>>> b == a
True

    数字类型用is比较有一些特殊,对于[-5, 256]区间内的small_ints,python会进行缓存,不会创建新的对象。

>>> a = 256
>>> b = 256
>>> a is b
True
>>> a == b
True
>>> a = 257
>>> b = 257
>>> a is b
False
>>> a == b
True

    字符串使用is的结果也不一定是相同。

>>> c = 'abc.com'
>>> b = 'abc.com'
>>> c is b
False
>>> c == b
True
>>> c = 'efg'
>>> d = 'efg'
>>> c is d
True
>>> c == d
True

    tuple,list,set的is==的结果都不一样。

>>> a = (1, 2, 3)
>>> b = (1, 2, 3)
>>> a == b
True
>>> a is b
False
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> a == b
True
>>> a is b
False
>>> a = set([1, 2, 3])
>>> b = set([1, 2, 3])
>>> a == b
True
>>> a is b
False

dict Comprehensions

    创建dict有如下几种方式。

>>> 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
>>> DIAL_CODES = [
... (86, 'China'),
... (91, 'India'),
... (81, 'Japan'),
... ]
>>> country_code = {country: code for code, country in DIAL_CODES}
>>> country_code
{'China': 86, 'India': 91, 'Japan': 81}

处理缺失值

    同一个逻辑的几种实现方式。

>>> my_dict = {}
>>> key = 'a'
>>> if key not in my_dict:
...     my_dict[key] = []
... 
>>> my_dict[key].append("value")
>>> my_dict
{'a': ['value']}
>>> my_dict = {}
>>> key = 'a'
>>> my_list = my_dict.get(key, [])
>>> my_list.append("value")
>>> my_dict[key] = my_list
>>> my_dict
{'a': ['value']}
>>> my_dict = {}
>>> key = 'a'
>>> my_list = my_dict.setdefault(key, [])
>>> my_list.append("value")
>>> my_dict
{'a': ['value']}
>>> my_dict = collections.defaultdict(list)
>>> key = 'a'
>>> my_dict[key].append("value")
>>> my_dict
defaultdict(<class 'list'>, {'a': ['value']})
>>> class ListDict(dict):
...     def __missing__(self, key):
...         return []
... 
>>> a = ListDict()
>>> my_list = a[key]
>>> my_list
[]
>>> my_list.append("value")
>>> a[key] = my_list
>>> a
{'a': ['value']}

    对于前三种方法,直接使用dict,setdefault会比前两种方法更高效。如果需要更灵活的对缺失值的处理,可以采用第四种方法(default_dict)和第五种方法(继承dict)。
    缺失值处理的实质就是__missing__这个特殊方法的调用,这个方法只会在__getitem__(就是像d[k]这种形式)碰到缺失值时才会调用,而对get__contains__(in方法)等其他dict的方法没有影响。所以在处理缺失值的时候,其他的方法也需要相应的处理。

其他类型的dict

collections.OrderedDict:按照key的插入顺序维护keys,可以以一个可预测的顺序对key进行遍历。还可以popitem第一个或者最一个元素。
collections.ChainMap:把几个dict放在一起进行搜索,搜索按照dict的顺序进行,在任意一个dict中找到key就会搜索成功。比如在变量查找时,可以先查找本地变量,再查找全局变量,最后查找系统变量。还有对命令行参数,可以先查找用户输入的变量,再查找变量的默认值。
collections.Counter:对每个key维护一个计数,可以通过update方法更新一个key增加计数,还有most_common返回出行次数最多的n个key。

Subclassing UserDict

    一般创建一个新的mapping类型通过继承UserDict会比继承dict要容易。主要原因在于built-in对象的一些实现会走捷径,导致你必须去重载方法,而继承UserDict不会有这些问题。具体的原因还会在后续的章节详细讲解。

Immutable Mappings

    MappingProxyType提供了一个原始mapping的动态识图,意味着原始的mapping的更新能够在这个mappingproxy中看到,但是不能通过他对原始的mapping进行更新。

>>> from types import MappingProxyType
>>> d={1:'A'}
>>> d_proxy = MappingProxyType(d)
>>> d_proxy
mappingproxy({1: 'A'})
>>> d_proxy[1]
'A'
>>> d_proxy[2] = 'x'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'mappingproxy' object does not support item assignment
>>> d[2] = 'B'
>>> d_proxy
mappingproxy({1: 'A', 2: 'B'})
>>> d_proxy[2]
'B'

Set

    set是一个唯一对象的集合,可以用于去重。set存储的元素必须是hashable的,但是set本身不是hashable的,但frozenset是hashable的。

>>> l = ['spam', 'spam', 'eggs', 'spam']
>>> set(l)
{'spam', 'eggs'}
>>> hash(set(l))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'set'

    set提供一些中缀操作符,如a | b表示union,a & b表示intersection,a - b表示difference。
    创建set使用{},但是要注意一个空的集合需要使用set()来创建,否则创建出来的是一个dict。

>>> s={1}
>>> type(s)
<class 'set'>
>>> s={}
>>> type(s)
<class 'dict'>
>>> s = {i for i in range(10)}
>>> s
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
>>> type(s)
<class 'set'>

dict and set Under the Hood

    核心就是hash table,也是为什么dict和set的元素都需要是hashable的。不过和java相比,python解决hash冲突使用的是开放寻址法,而java使用的是链接法。使用开放寻址法导致内存开销较大,一旦无法解决冲突,就需要扩充内存,并且进行元素的重新分布。不过适合于key的快速查找。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容