本文将涵盖在一些特殊用途的字典,如OrderedDict, defaultdict, ChainMap, Counter,它们都在 collections 类库下面,另外还有
types下面的MappingProxyType
weakref 下面的 WeakKeyDictionary, WeakValueDictionary
它主要的作用是字典排序, 标准的字典插入到字典中的键值对是没有顺序的,但是在OrderedDict中,会按照插入的顺序排序字典
from collections import OrderedDict
order_dict = OrderedDict()
#排序方式为按插入的先后顺序
order_dict['one'] = 1
order_dict[1] = 'one'
order_dict['two'] = 2
order_dict
OrderedDict([(‘one’, 1), (1, ‘one’), (‘two’, 2)])
上面生成的是一个OrderedDict对象,由于它是dict的子类,它具有dict的全部功能,这里可以进行直接转换
dict(order_dict)
{‘two’: 2, 1: ‘one’, ‘one’: 1}
这样一来, 由于按先后顺序排序,因此可以通过排序方法改变插入键值对的顺序,进而使得字典按我们要的方式排序
test_dict = dict([('one', 1), (1, 'one'), ('two', 2)])
# 以值的长度从小到大排序
OrderedDict(sorted(test_dict.items(), key=lambda t: len(str(t[1]))))
OrderedDict([(‘one’, 1), (‘two’, 2), (1, ‘one’)])
另外由于插入顺序的原因, 还可以使它成为一个队列(FIFO)或者堆栈的结构(LIFO)
order_dict['fifo'] = 'test'
order_dict
OrderedDict([(‘one’, 1), (1, ‘one’), (‘two’, 2), (‘fifo’, ‘test’)])
# 先进先出
order_dict.popitem(last=False)
order_dict
OrderedDict([(1, ‘one’), (‘two’, 2), (‘fifo’, ‘test’)])
#后进先出
order_dict['lifo'] = 'test'
order_dict.popitem(last=True)
order_dict
OrderedDict([(1, ‘one’), (‘two’, 2), (‘fifo’, ‘test’)])
在3.2版本中增加了move_to_end(key, last=True)方法,可以将某个key进行前后移动
order_dict.move_to_end(1, last=True)
order_dict
OrderedDict([(‘two’, 2), (‘fifo’, ‘test’), (1, ‘one’)])
注: 在3.5中还增加了 reverse反转方法,可以将OrderedDict倒过来
defaultdict 继承了dict,但增加魔法方法 __missing__(key)
,在默认key
没有找到的情况会进入这个方法 ,在这个方法中,当default_factory
参数不为none
时, 它会将default_factory
以没有参数的形式调用,并添加到这个key
上面,同时返回这个值,这个defaultdict
带来的好处就是,可以把没有在字典中的key
的值,直接拿来使用.
from collections import defaultdict
dd = defaultdict(dict)
dd['missing_key']['a'] = 1
dd['missing_key']
{'a': 1}
def factory_method():
abc = {'a': 1}
return abc
dd_0 = defaultdict(factory_method)
dd_0['missing_key']['b'] = 2
dd_0['missing_key']
{'a': 1, 'b': 2}
setdefault
方法效率更高,更简洁dl = defaultdict(list)
dl['missing_key'].append('something)
它非常适合来给一堆映射关系做操作,按照某种顺序和一定逻辑来处理这些映射关系,或查找,或更新删除
比如最直接点的例子就是,搜索python中某个程序的变量或者方法时,因为它可能在 locals
,globals
, builtins
中,如果找到了它就不继续往下搜索了, 所以可以将它们按顺序链起来,然后依次查找,找到即返回
from collections import ChainMap
import builtins
pylookup = ChainMap(locals(), globals(), vars(builtins))
下面举一个实际操作的例子,有三个字典,将它们链起来
dict1 = {'a': 'a1'}
dict2 = {'a': 'a2'}
dict3 = {'b': 'b3'}
dict_chain = ChainMap(dict1, dict2, dict3)
dict1
就返回了dict_chain['a']
‘a1’
b
也是按从左到右的顺序查找的dict_chain['b']
‘b3’
dict_chain['c'] = 'c1'
dict1
{‘a’: ‘a1’, ‘c’: ‘c1’}
maps
方法,来指定某个字典,比如往链上第二个字典中插入键值对dict_chain.maps[1]['d'] = 'd2'
dict2
{‘a’: ‘a2’, ‘d’: ‘d2’}
如果不想更改这个链上的值,但又希望能够找到,可以使用new_child
方法,它会添加一个新的节点到这个链上的第一个位置,所有的删除和修改操作只影响这个新添加的节点,因为new_child
方法返回的链,是把原来的链视为父链,并规定了只能查看不能修改父链.
dict4 = dict_chain.new_child()
dict_chain
ChainMap({‘c’: ‘c1’, ‘a’: ‘a1’}, {‘d’: ‘d2’, ‘a’: ‘a2’}, {‘b’: ‘b3’})
dict4.parents
ChainMap({‘c’: ‘c1’, ‘a’: ‘a1’}, {‘d’: ‘d2’, ‘a’: ‘a2’}, {‘b’: ‘b3’})
dict4['e'] = 'e4'
dict4
ChainMap({'e': 'e4'}, {'c': 'c1', 'a': 'a1'}, {'d': 'd2', 'a': 'a2'}, {'b': 'b3'})
dict4['a'] = 'test'
dict4
ChainMap({'a': 'test', 'e': 'e4'}, {'c': 'c1', 'a': 'a1'}, {'d': 'd2', 'a': 'a2'}, {'b': 'b3'})
dict4['b']
'b3'
del dict4['b']
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
C:\Python34\lib\collections\__init__.py in __delitem__(self, key)
865 try:
--> 866 del self.maps[0][key]
867 except KeyError:
KeyError: 'b'
During handling of the above exception, another exception occurred:
KeyError Traceback (most recent call last)
in ()
----> 1 del dict4['b']
C:\Python34\lib\collections\__init__.py in __delitem__(self, key)
866 del self.maps[0][key]
867 except KeyError:
--> 868 raise KeyError('Key not found in the first mapping: {!r}'.format(key))
869
870 def popitem(self):
KeyError: "Key not found in the first mapping: 'b'"
del dict4['a']
dict4
ChainMap({‘e’: ‘e4’}, {‘c’: ‘c1’, ‘a’: ‘a1’}, {‘d’: ‘d2’, ‘a’: ‘a2’}, {‘b’: ‘b3’})
ChainMap
同样可以使用 items
进行遍历,但是它会输出一种遍历顺序,优先考虑靠链前面的字典的键.for key, value in dict4.items():
print(key, value)
a a1
b b3
c c1
d d2
e e4
dict(dict4)
{'a': 'a1', 'b': 'b3', 'c': 'c1', 'd': 'd2', 'e': 'e4'}
* 注: 如何定制ChainMap,可以参考 library 文档, 这里边举了一个DeepChainMap的例子 *
主要用于给重复元素计数的, 3.1版本后新增了很多方法,官方文档中有很多很好的例子,
这里注意以下几点
from collections import Counter
c = Counter('abbbsdsdf')
c
Counter({‘a’: 1, ‘b’: 3, ‘d’: 2, ‘f’: 1, ‘s’: 2})
c['w']
0
c0 = Counter({'w': 2})
c | c0
Counter({‘a’: 1, ‘b’: 3, ‘d’: 2, ‘f’: 1, ‘s’: 2, ‘w’: 2})
毫无疑问它是一个mapping对象, 但是它是不可修改的, 比如在方法里面传一个字典对象,不希望在方法里面修改这个字典, 只允许访问操作,就可以用到它了。
from types import MappingProxyType
test_dict = {'a': 1}
dict_proxy = MappingProxyType(test_dict)
dict(dict_proxy)
{'a': 1}
dict_proxy.update({'b': 2})
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
in ()
----> 1 dict_proxy.update({'b': 2})
AttributeError: 'mappingproxy' object has no attribute 'update'
dict_proxy['b'] = 2
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in ()
----> 1 dict_proxy['b'] = 2
TypeError: 'mappingproxy' object does not support item assignment
dict_proxy['a'] = 'a'
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in ()
----> 1 dict_proxy['a'] = 'a'
TypeError: 'mappingproxy' object does not support item assignment
dict_proxy['a']
1
弱引用mapping对象, 以WeakValueDictionary来说, 当键对应的值除了mapping本身引用之外没有其他引用的情况下,会回收掉这个值,相应的这个键值对就不存在了
import weakref
class TestObject(object):
value = 'hello weakref'
d = weakref.WeakValueDictionary()
d[1] = TestObject()
除了 d[1] 引用这个对象之外没有其他引用,立即被垃圾回收器收回了
d[1]
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
in ()
----> 1 d[1]
C:\Python34\lib\weakref.py in __getitem__(self, key)
123
124 def __getitem__(self, key):
--> 125 o = self.data[key]()
126 if o is None:
127 raise KeyError(key)
KeyError: 1
d
obj = TestObject()
d[1] = obj
由于obj还被main模块所引用,因此可以访问到这个键值对
d[1]
<__main__.TestObject at 0x944650>
注: 对WeakValueDictionary对象使用valuerefs()方法进行遍历时,并不能保证这个字典不会改变,可能存在遍历过程中值被垃圾回收器收回的风险。
对于WeakKeyDictionary来说它类似于WeakValueDictionary, 只不过这时候弱引用作用于键上面。