关于python:确定字典中是否存在键

Determine whether a key is present in a dictionary

本问题已经有最佳答案,请猛点这里访问。

Possible Duplicate:
'has_key()' or 'in'?

我有一本Python字典,像:

1
mydict = {'name':'abc','city':'xyz','country','def'}

我想检查一下字典里有没有一个键。我很想知道下面两个案例中哪一个更可取,为什么呢?

1
2
1> if mydict.has_key('name'):
2> if 'name' in mydict:


1
if 'name' in mydict:

是首选的Python型。不鼓励使用has_key(),并且该方法已在python 3中被删除。


正如马蒂诺的反应一样,最好的解决办法往往是不检查。例如,代码

1
2
3
4
if x in d:
    foo = d[x]
else:
    foo = bar

通常是写的

1
foo = d.get(x, bar)

它更短,更直接地表达了你的意思。

另一个常见的例子是

1
2
3
4
if x not in d:
    d[x] = []

d[x].append(foo)

可以重写的

1
d.setdefault(x, []).append(foo)

或者用collections.defaultdict(list)代替d,再写得更好。

1
d[x].append(foo)


在字节码方面,in保存了LOAD_ATTR并用COMPARE_OP代替了CALL_FUNCTION

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> dis.dis(indict)
  2           0 LOAD_GLOBAL              0 (name)
              3 LOAD_GLOBAL              1 (d)
              6 COMPARE_OP               6 (in)
              9 POP_TOP            


>>> dis.dis(haskey)
  2           0 LOAD_GLOBAL              0 (d)
              3 LOAD_ATTR                1 (haskey)
              6 LOAD_GLOBAL              2 (name)
              9 CALL_FUNCTION            1
             12 POP_TOP

我的感觉是,in更易读,在我能想到的任何情况下都是首选。

在性能方面,定时反映了操作码

1
2
3
4
5
$ python -mtimeit -s'd = dict((i, i) for i in range(10000))'"'foo' in d"
 10000000 loops, best of 3: 0.11 usec per loop

$ python -mtimeit -s'd = dict((i, i) for i in range(10000))'"d.has_key('foo')"
  1000000 loops, best of 3: 0.205 usec per loop

in的速度几乎是前者的两倍。


我的答案是"两者都不是"。

我认为做事情的最"Python式"的方法是不事先检查字典中是否有密钥,而是编写假定它存在的代码,并捕获由于它不存在而引发的任何密钥错误。

这通常是通过将代码包含在try...except子句中来完成的,这是一个众所周知的习惯用法,通常表示为"请求原谅比请求允许更容易",或者缩写为eafp,这基本上意味着在做任何事情之前最好尝试并捕获错误,而不是确保一切正常。当您可以优雅地处理异常而不是试图避免它们时,为什么要验证不需要验证的内容?因为它的可读性通常更高,如果密钥不在的概率很低(或者可能存在的任何先决条件),代码的速度就会更快。

当然,这并不适用于所有情况,也不是每个人都同意这一理念,因此您需要根据具体情况自行决定。不足为奇,与此相反的是"三思而后行"的lbyl。

作为一个简单的例子,请考虑:

1
2
3
4
5
if 'name' in dct:
    value = dct['name'] * 3
else:
    logerror('"%s" not found in dictionary, using default' % name)
    value = 42

VS

1
2
3
4
5
try:
    value = dct['name'] * 3
except KeyError:
    logerror('"%s" not found in dictionary, using default' % name)
    value = 42

尽管在这种情况下,代码量几乎完全相同,但第二个代码不会首先花费时间进行检查,因此可能稍微快一点(尝试一下……不过,除了块不是完全免费的,所以在这里可能不会有太大的区别)。

一般来说,提前测试通常会涉及到更多的内容,而不这样做可以节省大量的成本。也就是说,由于其他答案中所述的原因,if 'name' in dict:更好。

如果您对这个主题感兴趣,那么来自python邮件列表存档的标题为"eafp vs lbyl(was re:a little dissafess so far)"的消息很可能解释了这两种方法之间的区别,比我在这里做的更好。还有一个很好的讨论,是关于《简而言之,python》这本书中的两种方法,这本书是亚历克斯·马泰利在第6章的第2版,题目是"错误检查策略"。(我看到现在有一个更新的第三版,于2017年出版,涵盖了python 2.7和3.x)。