理解Python中的defaultdict

在Python中,如果访问字典中不存在的键时会引发KeyError的异常。但是如果字典中的键如果能够有默认的值有时候是非常方便的。比如以下的例子:

strings ={'puppy','kitten','puppy','puppy','wesel','puppy','kitten','puppy}
counts = {}
for i in strings:
	counts[i] += 1

以上这个例子是用来统计单词出现的次数,将单词作为count中的键,单词每出现一次就加1.事实上这段代码是会抛出KeyError的。

有以下几个办法能够处理这个问题。

使用判断语句检查

我们可以在每次取得单词时候,检查这个单词是否在字典中有默认值,没有就赋一个默认值。

strings ={'puppy','kitten','puppy','puppy','wesel','puppy','kitten','puppy}
counts = {}
for i in strings:
	if i not in count:
		counts[i] = 0
	counts[i] += 1
dict.setdefault()方法

我们也可以使用dict.setdefault()方法来设置默认值:这个方法结构两个参数,一个是键的名称,另一个是默认值。如果键已经存在字典中就返回它的值,如果没有就将默认值保存并且返回该默认值。用dict.setdefault()重写上面的例子

setdefault(key[, default])

If key is in the dictionary, return its value. If not, insert key with a value of default and return default. default defaults to None

strings ={'puppy','kitten','puppy','puppy','wesel','puppy','kitten','puppy}
counts = {}
for i in strings:
	counts.setdefault(i,0)
	counts[i] += 1

下面这种方法就是我们本篇博客的主角collections.defaultdict类

collections.defaultdict

class collections.``defaultdict([default_factory[, ]])

defaultdict类返回一个类似于的字典对象,第一个参数给default_factory属性赋值,其它的参数都传递给dict构造器。通俗来说就是defaultdict类的初始化函数接收一个类型作为参数,当访问的键不存在,实例化一个值作为默认值

>>> from collections import defaultdict
>>> dd = defaultdict(list)
>>> dd
defaultdict(<class 'list'>, {})
>>> dd['lei']
[]
>>> dd
defaultdict(<class 'list'>, {'lei': []})
>>> dd['wang'].append(22)
>>> dd
defaultdict(<class 'list'>, {'lei': [], 'wang': [22]})

但是这种使用仅限于直接通过访问字典的键dict[key]或者dict._getitem_()这两种方式,如下面的例子,其它的使用都不行,具体原因稍后介绍。

>>> 'xu' in dd
False
>>> dd.pop('xu')
Traceback (most recent call last):
  File "<pyshell#9>", line 1, in <module>
    dd.pop('xu')
KeyError: 'xu'
>>> dd.get('xu')
>>> dd['xu']
[]

该类除了接收类型名称来作为初始化函数的参数,还可以使用任何不带参数的可调用函数作为参数,函数的返回结果就作为默认值,这样可以使得默认值的取值更加灵活。下面的例子是介绍这种用法。

>>> from collections import defaultdict
>>> def one():
	return 1

>>> dd = defaultdict(one)
>>> dd
defaultdict(<function one at 0x1101ff2f0>, {})
>>> dd['lei']
1
>>> dd
defaultdict(<function one at 0x1101ff2f0>, {'lei': 1})

同样我们也可以使用匿名函数lambda来作为参数,比如说:

from collections import defaultdict

strings ={'puppy','kitten','puppy','puppy','wesel','puppy','kitten','puppy}
counts = defaultdict(lambda: 0)
for i in strings:
	counts[i] +=1

为什么defaultdict类可以实现这样的用法呢,因为它支持_missing_()方法

class collections.``defaultdict([default_factory[, ]])

__missing__(key)

If the default_factory attribute is None, this raises a KeyError exception with the key as argument.

If default_factory is not None, it is called without arguments to provide a default value for the given key, this value is inserted in the dictionary for the key, and returned.

If calling default_factory raises an exception this exception is propagated unchanged.

This method is called by the __getitem__() method of the dict class when the requested key is not found; whatever it returns or raises is then returned or raised by __getitem__().

Note that __missing__() is not called for any operations besides __getitem__(). This means that get() will, like normal dictionaries, return None as a default rather than usingdefault_factory

从第二句我们可以看到如果default_factory不是None,调用时会给给定的key一个默认值,这个默认值会保存在key对应的之中,并且被返回。第四句说明这个方法只会被 __getitem__()方法调用,dict[key]这种形式实际上是__getitem__方法的简化形式。

当__getitem__()方法访问一个不存在的键时会调用__missing__()方法获得默认的值,将该键添加到字典中去

>>>print(defaultdict.__missing__.__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
实现一个defaultdict功能

”dict“python文档中还介绍,如果dict的子类实现了__missing__方法,当访问不存在的键时,dict[key]会调用__missing__()方法来获得默认值。下面我们可以进一步实验,定义一个dict的子类Missing并且实现__missing__()方法。

>>> class Missing(dict):
	def __missing__(self,key):
		return 'missing'	
>>> d = Missing()
>>> d
{}
>>> d['lei']
'missing'
>>> d
{}

返回结果表明__missing__()方法确实发挥了作用,但是key没有添加到字典当中去,我们再修改一下代码。

>>> class defaultdict(dict):
	def __missing__(self,key):
		self[key] = 'wanglei'
		return 'wanglei'

	
>>> dd=defaultdict()
>>> dd
{}
>>> dd['foo']
'wanglei'
>>> dd
{'foo': 'wanglei'}
在旧的Python版本中实现defaultdict功能

本来这个我不想写了,但是我觉得有必要综合一下上面所说的。defaultdict类是在Python2.5中加入进来的,但是我们可以实现一个兼容defaultdict的类。

class defaultdict(dict):
    # 首先当 __getitem__()方法访问键失败的时候,调用__missing__方法。
	def __getitem__(self,key):
		try:
			return dict.__getitem__(self,key)
		except KeyError:
			return self.__missing__(key)
	# 实现__missing__方法来给设置默认值
	def __missing__(key):
		self[key] =value = self.default_factory()
		return value

然后defaultdict类的初始化函数__init__()需要接收类型或者可调用函数的参数

class defaultdict(dict):
    def __init__(self,default_factory=None,*a,**kwag):
    	dict.__init__(self,*a,**kwag)
    	self.default_factory = default_factory
    # 首先当 __getitem__()方法访问键失败的时候,调用__missing__方法。
	def __getitem__(self,key):
		try:
			return dict.__getitem__(self,key)
		except KeyError:
			return self.__missing__(key)
	# 实现__missing__方法来给设置默认值
	def __missing__(key):
		self[key] =value = self.default_factory()
		return value
Reference

本文主要参考了这篇

http://kodango.com/understand-defaultdict-in-python

  • 10
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值