一个“宽容”的词典

9
我想知道如何创建一个容错的字典(如果出现KeyError错误,则返回默认值)。
在下面的代码示例中,如果出现KeyError错误,我将会得到一个异常。
a = {'one':1,'two':2}
print a['three']

为了避免出现这种情况,我需要捕获异常或使用get方法。 但是我不想在我的字典中这样做...

2
collections.defaultdict 是一个内置的解决方案。 - Nathan Ernst
5个回答

22
import collections
a = collections.defaultdict(lambda: 3)
a.update({'one':1,'two':2})
print a['three']
< p >发出所需的3。 您还可以自己子类化dict并覆盖__missing__,但是当defaultdict行为(忽略正在查找的确切缺失键)非常适合您时,这没有多大意义...

编辑...除非你担心每次查找缺失键(这是defaultdict的语义之一)都会使a增加一个条目,并希望获得更慢的行为但节省一些内存。 例如,就内存而言...:

>>> import sys
>>> a = collections.defaultdict(lambda: 'blah')
>>> print len(a), sys.getsizeof(a)
0 140
>>> for i in xrange(99): _ = a[i]
... 
>>> print len(a), sys.getsizeof(a)
99 6284

...defaultdict最初为空,现在具有我们查找的99个先前缺失的键,并且占用6284字节(与它为空时占用的140字节相比)。

另一种方法是...

>>> class mydict(dict):
...   def __missing__(self, key): return 3
... 
>>> a = mydict()
>>> print len(a), sys.getsizeof(a)
0 140
>>> for i in xrange(99): _ = a[i]
... 
>>> print len(a), sys.getsizeof(a)
0 140

完全消除了这种内存开销,正如您所见。当然,性能是另一个问题:

$ python -mtimeit -s'import collections; a=collections.defaultdict(int); r=xrange(99)' 'for i in r: _=a[i]'
100000 loops, best of 3: 14.9 usec per loop

$ python -mtimeit -s'class mydict(dict):
>   def __missing__(self, key): return 0
> ' -s'a=mydict(); r=xrange(99)' 'for i in r: _=a[i]'
10000 loops, best of 3: 92.9 usec per loop

defaultdict会在查找时添加(之前不存在的)键,因此当下次查找该键时,它会变得更快,而mydict(覆盖了__missing__以避免该添加)则每次都需要支付“缺失键查找开销”。

当然,你是否关心这两个问题(性能与内存占用)完全取决于你的具体用例。无论如何,意识到这种权衡是一个好主意!-)


3
警告:当defaultdict返回给定键的默认值时,它会将一个新项目插入到自身中。这将读操作转换为潜在的写操作,并意味着查找大量不存在的键将使其快速增长。http://docs.python.org/library/collections.html#collections.defaultdict.__missing__ - ʇsәɹoɈ
非常棒的文章!你倒数第二段似乎与示例无关,因为你从未重复使用相同的键。因此,似乎defaultdict即使您不重复使用键也更快,如果您这样做甚至更快。是这样吗? - new name
@Jeff,timeit在测量语句时会对其进行循环,因此它会重复该语句--在这种情况下,是for i in r循环。 - Alex Martelli
+1 我从未意识到这两种方法的性能/内存权衡。谢谢你启发我。 - snapshoe

7
版本 2.5 中的新功能:如果字典的子类定义了一个名为 __missing__() 的方法,则在键 key 不在字典中时,d[key] 操作将使用 key 作为参数调用该方法。如果键不存在,则 d[key] 操作返回或引发 __missing__(key) 调用返回或引发的任何内容。没有其他操作或方法会调用 __missing__()。如果未定义 __missing__(),则会引发 KeyError。__missing__() 必须是一个方法;它不能是实例变量。有关示例,请参见 collections.defaultdict。

http://docs.python.org/library/stdtypes.html


6

以下是如何按照NullUserException的建议子类化dict

>>> class forgiving_dict(dict):
...     def __missing__(self, key):
...         return 3
...
>>> a = forgiving_dict()
>>> a.update({'one':1,'two':2})
>>> print a['three']
3

这个答案和Alex的答案之间有一个很大的区别,就是丢失的键没有被添加到字典中。
>>> print a
{'two': 2, 'one': 1}

如果你期望出现很多错误,这就非常重要了。


3

你可能需要使用defaultdict(我相信至少需要python2.5)

from collections import defaultdict
def default(): return 'Default Value'
d = defaultdict(default)
print(d['?'])

构造函数中传递的函数告诉类需要返回什么作为默认值。有关其他示例,请参见文档

0
有时候你真正想要的是 .setdefault(),这个方法并不是很直观,但它可以“返回指定的键,如果不存在,则将该键设置为此值”。
下面是一个使用 setdefault() 的好例子:
collection = {}
for elem in mylist:
    key = key_from_elem(elem)
    collection.setdefault(key, []).append(elem)

这将使我们能够创建类似于以下的字典:{'key1':[elem1, elem3], 'key2':[elem3]},而无需进行丑陋的检查以查看是否已经存在键并为其创建列表。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接