工作需要,想写一个类似于collections.defaultdict
的字典类型,但是默认值是使用key进行初始化的。参看了defaultdict的实现代码,改造了一个新的类型defaultdictByKey
。下面先介绍defaultdict的原理再介绍defaultdictByKey.
defaultdict的原理
defaultdict的直接父类是dict。与dict最大的不同是,当key不存在时,defaultdict会生成一个给定的默认value给key当作value,也就是defaultdict是带有默认值的dict。用法如下:
# 给定一个确定的数值或字符串作为默认值
from collections import defaultdict as ddt
a_dic = ddt(lambda : 1.0)
a_dic["A"]
>>> 1.0
word_count_mapping = ddt(int)
for word in ["word1", "word2", "word3", "word1"]:
word_count_mapping[word] += 1
print(word_count_mapping)
>>> defaultdict(<type 'int'>, {'word1': 2, 'word3': 1, 'word2': 1})
print(word_count_mapping['word3'])
>>> 0
defaultdict包括两部分:一设置默认值的生成器,二判断key不存在并生成对象并赋值。
defaultdict
的源码如下。结合代码我们讲述如何设置给定类型生成器和给新key设置默认值。
class defaultdict(dict):
"""
defaultdict(default_factory[, ...]) --> dict with default factory
The default factory is called without arguments to produce
a new value when a key is not present, in __getitem__ only.
A defaultdict compares equal to a dict with the same items.
All remaining arguments are treated the same as if they were
passed to the dict constructor, including keyword arguments.
"""
...# 省略其他的代码,一是与此不相关,二是有部分我也不懂你问了我解释不出来,尴尬!
def __init__(self, default_factory=None, **kwargs): # known case of _collections.defaultdict.__init__
# 伪代码
self.default_factory = default_factory
def __missing__(self, key): # real signature unknown; restored from __doc__
"""
__missing__(key) # Called by __getitem__ for missing key; pseudo-code:
if self.default_factory is None: raise KeyError((key,))
self[key] = value = self.default_factory()
return value
"""
# 伪代码
if self.default_factory is None:
raise KeyError(key)
else:
self[key] = value = self.default_factory()
return value
default_factory = property(lambda self: object(), lambda self, v: None, lambda self: None) # default
"""Factory for default value called by __missing__()."""
设置给定类型生成器,其__init__
的第一个非self参数为default_factory
,也就是生成器,factory这个名字和工厂模式相关,确实也是如此,工厂模式属于一种生成式设计模式,这里这个factory生成默认值用的,其常用调用方式如下:
>>> int()
0
>>> word_freq_mapping = defaultdict(int) # 词频映射表,每个新单词的默认值为int()也就是0.
>>> word_freq_mapping[u"大学"] += 1
>>> # 某国家的某个城市的人口数量映射,这是一个二级映射表
>>> country_city_population_mapping = defaultdict(lambda : defaultdict(int))
>>> country_city_population_mapping["china"]['beijing'] += 20000000
给新key设置默认值。当我们调用dict的某个key的value时,如word_freq_mapping[u"大学"] += 1
其实是调用了dict.__getitem__(key)
(不要问为什么,这涉及到底层的python实现和架构),当key不存在时,会直接调用__missing__(self, key)
函数。在defaultdict.__missing__
中,调用给定生成器生成value,并赋给这个key,最后返回这个value。如果我们没有设置生成器,那么就会报KeyError异常。如下:
>>> from collections import defaultdict as ddt
>>> s = ddt() # 没有设置生成器
>>> s[3] #
KeyError Traceback (most recent call last)
<ipython-input-22-a93d6738dc8e> in <module>()
1 from collections import defaultdict as ddt
2 ss = ddt()
----> 3 print ss[3]
KeyError: 3
defaultdictByKey实现
在__missing__
的代码中,我们也可以看到,生成器是无参的。而key是参数已经传过来了,那么我们只需要重写这个函数,调用我们给定的有参生成器,即可达到我们的目的。代码如下:
class defaultdictByKey(dict):
"""
当key不存在时,将key传给自定义的工厂,生成一个value作为key的value。
>>> key_mapping = defaultdictByKey(lambda x:[x])
>>> key_mapping[1]
[1]
"""
def __init__(self, factory=None, **kwargs):
super(defaultdictByKey, self).__init__(**kwargs)
self._factory = factory
def __missing__(self, key):
if self._factory != None:
self[key] = value = self._factory(key)
return value
else:
raise KeyError(key)