宽容的字典

9 投票
5 回答
724 浏览
提问于 2025-04-16 01:59

我想知道怎么创建一个宽容的字典,也就是如果出现找不到键的错误,它能返回一个默认值。

在下面的代码示例中,如果我尝试访问一个不存在的键,就会出现一个键错误(KeyError);比如说:

a = {'one':1,'two':2}
print a['three']

为了避免这个错误,我需要要么捕获这个异常,要么使用get方法。

我希望我的字典不需要这样处理...

5 个回答

6

这里是如何根据NullUserException的建议来创建一个字典的子类

>>> 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}

如果你预期会有很多缺失的键,这一点是非常重要的

7

在2.5版本中,如果你有一个字典的子类,并且这个子类定义了一个叫做 __missing__() 的方法,那么当你尝试访问一个不存在的键时,比如 d[key],这个操作会自动调用 __missing__() 方法,并把你查找的键作为参数传进去。接着,d[key] 的结果就会是 __missing__(key) 方法返回的内容,或者如果这个方法抛出了错误,也会抛出那个错误。如果没有定义 __missing__() 方法,系统会抛出一个叫 KeyError 的错误。需要注意的是,__missing__() 必须是一个方法,而不能是一个实例变量。想看个例子的话,可以参考 collections.defaultdict。

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

22
import collections
a = collections.defaultdict(lambda: 3)
a.update({'one':1,'two':2})
print a['three']

这个代码会在需要的时候返回 3。你也可以自己创建一个字典的子类,并重写 __missing__ 方法,但这样做其实没什么意义,因为 defaultdict 的行为(忽略正在查找的具体缺失键)对你来说已经很合适了...

编辑 ...除非你担心每次查找缺失的键时,a 会多一个条目(这是 defaultdict 的特性),你可能更希望速度慢一点,但能节省一些内存。比如,从内存的角度来看...:

>>> 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__ 来避免添加)每次查找缺失的键时都会有“缺失键查找开销”。

你是否在意这两个问题(性能和内存占用)完全取决于你的具体使用场景。当然,了解这种权衡是个好主意!-)

撰写回答