理解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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章